@gitbook/react-openapi 1.1.5 → 1.1.6
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 +15 -0
- package/dist/OpenAPICodeSample.d.ts +9 -0
- package/dist/OpenAPICodeSample.jsx +117 -58
- package/dist/OpenAPICodeSampleInteractive.d.ts +10 -0
- package/dist/OpenAPICodeSampleInteractive.jsx +78 -0
- package/dist/OpenAPISchemaName.jsx +7 -6
- package/dist/code-samples.d.ts +1 -2
- package/dist/code-samples.js +2 -2
- package/dist/generateSchemaExample.d.ts +31 -2
- package/dist/generateSchemaExample.js +307 -24
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +11 -7
- package/package.json +3 -3
- package/src/OpenAPICodeSample.tsx +176 -78
- package/src/OpenAPICodeSampleInteractive.tsx +114 -0
- package/src/OpenAPISchemaName.tsx +11 -6
- package/src/code-samples.ts +3 -3
- package/src/generateSchemaExample.ts +412 -25
- package/src/resolveOpenAPIOperation.test.ts +6 -6
- package/src/utils.ts +13 -10
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
|
2
|
-
export declare function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject
|
|
2
|
+
export declare function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject;
|
|
3
3
|
export declare function createStateKey(key: string, scope?: string): string;
|
|
4
4
|
/**
|
|
5
5
|
* Resolve the description of an object.
|
package/dist/utils.js
CHANGED
|
@@ -26,16 +26,20 @@ function hasDescription(object) {
|
|
|
26
26
|
* Resolve the description of an object.
|
|
27
27
|
*/
|
|
28
28
|
export function resolveDescription(object) {
|
|
29
|
-
//
|
|
29
|
+
// Resolve description from the object first
|
|
30
|
+
if (hasDescription(object)) {
|
|
31
|
+
return 'x-gitbook-description-html' in object &&
|
|
32
|
+
typeof object['x-gitbook-description-html'] === 'string'
|
|
33
|
+
? object['x-gitbook-description-html'].trim()
|
|
34
|
+
: typeof object.description === 'string'
|
|
35
|
+
? object.description.trim()
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
// If the object has no description, try to resolve it from the items
|
|
30
39
|
if ('items' in object && typeof object.items === 'object' && hasDescription(object.items)) {
|
|
31
40
|
return resolveDescription(object.items);
|
|
32
41
|
}
|
|
33
|
-
return
|
|
34
|
-
typeof object['x-gitbook-description-html'] === 'string'
|
|
35
|
-
? object['x-gitbook-description-html'].trim()
|
|
36
|
-
: typeof object.description === 'string'
|
|
37
|
-
? object.description.trim()
|
|
38
|
-
: undefined;
|
|
42
|
+
return undefined;
|
|
39
43
|
}
|
|
40
44
|
/**
|
|
41
45
|
* Extract descriptions from an object.
|
package/package.json
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"default": "./dist/index.js"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"version": "1.1.
|
|
11
|
+
"version": "1.1.6",
|
|
12
12
|
"sideEffects": false,
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@gitbook/openapi-parser": "workspace:*",
|
|
15
|
-
"@scalar/api-client-react": "^1.
|
|
16
|
-
"@scalar/oas-utils": "^0.2.
|
|
15
|
+
"@scalar/api-client-react": "^1.2.5",
|
|
16
|
+
"@scalar/oas-utils": "^0.2.120",
|
|
17
17
|
"clsx": "^2.1.1",
|
|
18
18
|
"flatted": "^3.2.9",
|
|
19
19
|
"json-xml-parse": "^1.3.0",
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
|
+
import {
|
|
3
|
+
OpenAPIMediaTypeExamplesBody,
|
|
4
|
+
OpenAPIMediaTypeExamplesSelector,
|
|
5
|
+
} from './OpenAPICodeSampleInteractive';
|
|
2
6
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
|
3
7
|
import { ScalarApiButton } from './ScalarApiButton';
|
|
4
8
|
import { StaticSection } from './StaticSection';
|
|
5
|
-
import { type
|
|
6
|
-
import {
|
|
9
|
+
import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples';
|
|
10
|
+
import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
|
|
7
11
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
8
12
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
9
13
|
import { getDefaultServerURL } from './util/server';
|
|
10
14
|
import { checkIsReference, createStateKey } from './utils';
|
|
11
15
|
|
|
16
|
+
const CUSTOM_CODE_SAMPLES_KEYS = ['x-custom-examples', 'x-code-samples', 'x-codeSamples'] as const;
|
|
17
|
+
|
|
12
18
|
/**
|
|
13
19
|
* Display code samples to execute the operation.
|
|
14
20
|
* It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
|
|
@@ -16,6 +22,43 @@ import { checkIsReference, createStateKey } from './utils';
|
|
|
16
22
|
export function OpenAPICodeSample(props: {
|
|
17
23
|
data: OpenAPIOperationData;
|
|
18
24
|
context: OpenAPIContextProps;
|
|
25
|
+
}) {
|
|
26
|
+
const { data } = props;
|
|
27
|
+
|
|
28
|
+
// If code samples are disabled at operation level, we don't display the code samples.
|
|
29
|
+
if (data.operation['x-codeSamples'] === false) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const customCodeSamples = getCustomCodeSamples(props);
|
|
34
|
+
|
|
35
|
+
// If code samples are disabled at the top-level and not custom code samples are defined,
|
|
36
|
+
// we don't display the code samples.
|
|
37
|
+
if (data['x-codeSamples'] === false && !customCodeSamples) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const samples = customCodeSamples ?? generateCodeSamples(props);
|
|
42
|
+
|
|
43
|
+
if (samples.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
|
|
49
|
+
<StaticSection header={<OpenAPITabsList />} className="openapi-codesample">
|
|
50
|
+
<OpenAPITabsPanels />
|
|
51
|
+
</StaticSection>
|
|
52
|
+
</OpenAPITabs>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate code samples for the operation.
|
|
58
|
+
*/
|
|
59
|
+
function generateCodeSamples(props: {
|
|
60
|
+
data: OpenAPIOperationData;
|
|
61
|
+
context: OpenAPIContextProps;
|
|
19
62
|
}) {
|
|
20
63
|
const { data, context } = props;
|
|
21
64
|
|
|
@@ -51,97 +94,104 @@ export function OpenAPICodeSample(props: {
|
|
|
51
94
|
const requestBody = !checkIsReference(data.operation.requestBody)
|
|
52
95
|
? data.operation.requestBody
|
|
53
96
|
: undefined;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
(searchParams.size ? `?${searchParams.toString()}` : ''),
|
|
64
|
-
method: data.method,
|
|
65
|
-
body: requestBodyContent ? generateMediaTypeExample(requestBodyContent[1]) : undefined,
|
|
66
|
-
headers: {
|
|
67
|
-
...getSecurityHeaders(data.securities),
|
|
68
|
-
...headersObject,
|
|
69
|
-
...(requestBodyContent
|
|
70
|
-
? {
|
|
71
|
-
'Content-Type': requestBodyContent[0],
|
|
72
|
-
}
|
|
73
|
-
: undefined),
|
|
74
|
-
},
|
|
97
|
+
|
|
98
|
+
const url =
|
|
99
|
+
getDefaultServerURL(data.servers) +
|
|
100
|
+
data.path +
|
|
101
|
+
(searchParams.size ? `?${searchParams.toString()}` : '');
|
|
102
|
+
|
|
103
|
+
const genericHeaders = {
|
|
104
|
+
...getSecurityHeaders(data.securities),
|
|
105
|
+
...headersObject,
|
|
75
106
|
};
|
|
76
107
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const customSamples = data.operation[key];
|
|
95
|
-
if (customSamples && Array.isArray(customSamples)) {
|
|
96
|
-
customCodeSamples = customSamples
|
|
97
|
-
.filter((sample) => {
|
|
98
|
-
return (
|
|
99
|
-
typeof sample.label === 'string' &&
|
|
100
|
-
typeof sample.source === 'string' &&
|
|
101
|
-
typeof sample.lang === 'string'
|
|
102
|
-
);
|
|
103
|
-
})
|
|
104
|
-
.map((sample, index) => ({
|
|
105
|
-
key: `redocly-${sample.lang}-${index}`,
|
|
106
|
-
label: sample.label,
|
|
107
|
-
body: context.renderCodeBlock({
|
|
108
|
-
code: sample.source,
|
|
109
|
-
syntax: sample.lang,
|
|
108
|
+
const mediaTypeRendererFactories = Object.entries(requestBody?.content ?? {}).map(
|
|
109
|
+
([mediaType, mediaTypeObject]) => {
|
|
110
|
+
return (generator: CodeSampleGenerator) => {
|
|
111
|
+
const mediaTypeHeaders = {
|
|
112
|
+
...genericHeaders,
|
|
113
|
+
'Content-Type': mediaType,
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
mediaType,
|
|
117
|
+
element: context.renderCodeBlock({
|
|
118
|
+
code: generator.generate({
|
|
119
|
+
url,
|
|
120
|
+
method: data.method,
|
|
121
|
+
body: undefined,
|
|
122
|
+
headers: mediaTypeHeaders,
|
|
123
|
+
}),
|
|
124
|
+
syntax: generator.syntax,
|
|
110
125
|
}),
|
|
111
|
-
|
|
112
|
-
|
|
126
|
+
examples: generateMediaTypeExamples(mediaTypeObject, {
|
|
127
|
+
mode: 'write',
|
|
128
|
+
}).map((example) => ({
|
|
129
|
+
example,
|
|
130
|
+
element: context.renderCodeBlock({
|
|
131
|
+
code: generator.generate({
|
|
132
|
+
url,
|
|
133
|
+
method: data.method,
|
|
134
|
+
body: example.value,
|
|
135
|
+
headers: mediaTypeHeaders,
|
|
136
|
+
}),
|
|
137
|
+
syntax: generator.syntax,
|
|
138
|
+
}),
|
|
139
|
+
})),
|
|
140
|
+
} satisfies MediaTypeRenderer;
|
|
141
|
+
};
|
|
113
142
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// Code samples can be disabled at the top-level or at the operation level
|
|
117
|
-
// If code samples are defined at the operation level, it will override the top-level setting
|
|
118
|
-
const codeSamplesDisabled =
|
|
119
|
-
data['x-codeSamples'] === false || data.operation['x-codeSamples'] === false;
|
|
120
|
-
const samples = customCodeSamples ?? (!codeSamplesDisabled ? autoCodeSamples : []);
|
|
143
|
+
);
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
145
|
+
return codeSampleGenerators.map((generator) => {
|
|
146
|
+
if (mediaTypeRendererFactories.length > 0) {
|
|
147
|
+
const renderers = mediaTypeRendererFactories.map((generate) => generate(generator));
|
|
148
|
+
return {
|
|
149
|
+
key: `default-${generator.id}`,
|
|
150
|
+
label: generator.label,
|
|
151
|
+
body: <OpenAPIMediaTypeExamplesBody data={data} renderers={renderers} />,
|
|
152
|
+
footer: (
|
|
153
|
+
<OpenAPICodeSampleFooter renderers={renderers} data={data} context={context} />
|
|
154
|
+
),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
key: `default-${generator.id}`,
|
|
159
|
+
label: generator.label,
|
|
160
|
+
body: context.renderCodeBlock({
|
|
161
|
+
code: generator.generate({
|
|
162
|
+
url,
|
|
163
|
+
method: data.method,
|
|
164
|
+
body: undefined,
|
|
165
|
+
headers: genericHeaders,
|
|
166
|
+
}),
|
|
167
|
+
syntax: generator.syntax,
|
|
168
|
+
}),
|
|
169
|
+
footer: <OpenAPICodeSampleFooter data={data} renderers={[]} context={context} />,
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
125
173
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
174
|
+
export interface MediaTypeRenderer {
|
|
175
|
+
mediaType: string;
|
|
176
|
+
element: React.ReactNode;
|
|
177
|
+
examples: Array<{
|
|
178
|
+
example: OpenAPIV3.ExampleObject;
|
|
179
|
+
element: React.ReactNode;
|
|
180
|
+
}>;
|
|
133
181
|
}
|
|
134
182
|
|
|
135
183
|
function OpenAPICodeSampleFooter(props: {
|
|
136
184
|
data: OpenAPIOperationData;
|
|
185
|
+
renderers: MediaTypeRenderer[];
|
|
137
186
|
context: OpenAPIContextProps;
|
|
138
187
|
}) {
|
|
139
|
-
const { data, context } = props;
|
|
188
|
+
const { data, context, renderers } = props;
|
|
140
189
|
const { method, path } = data;
|
|
141
190
|
const { specUrl } = context;
|
|
142
191
|
const hideTryItPanel = data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'];
|
|
192
|
+
const hasMediaTypes = renderers.length > 0;
|
|
143
193
|
|
|
144
|
-
if (hideTryItPanel) {
|
|
194
|
+
if (hideTryItPanel && !hasMediaTypes) {
|
|
145
195
|
return null;
|
|
146
196
|
}
|
|
147
197
|
|
|
@@ -151,11 +201,59 @@ function OpenAPICodeSampleFooter(props: {
|
|
|
151
201
|
|
|
152
202
|
return (
|
|
153
203
|
<div className="openapi-codesample-footer">
|
|
154
|
-
|
|
204
|
+
{hasMediaTypes ? (
|
|
205
|
+
<OpenAPIMediaTypeExamplesSelector data={data} renderers={renderers} />
|
|
206
|
+
) : (
|
|
207
|
+
<span />
|
|
208
|
+
)}
|
|
209
|
+
{!hideTryItPanel && <ScalarApiButton method={method} path={path} specUrl={specUrl} />}
|
|
155
210
|
</div>
|
|
156
211
|
);
|
|
157
212
|
}
|
|
158
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Get custom code samples for the operation.
|
|
216
|
+
*/
|
|
217
|
+
function getCustomCodeSamples(props: {
|
|
218
|
+
data: OpenAPIOperationData;
|
|
219
|
+
context: OpenAPIContextProps;
|
|
220
|
+
}) {
|
|
221
|
+
const { data, context } = props;
|
|
222
|
+
|
|
223
|
+
let customCodeSamples: null | Array<{
|
|
224
|
+
key: string;
|
|
225
|
+
label: string;
|
|
226
|
+
body: React.ReactNode;
|
|
227
|
+
}> = null;
|
|
228
|
+
|
|
229
|
+
CUSTOM_CODE_SAMPLES_KEYS.forEach((key) => {
|
|
230
|
+
const customSamples = data.operation[key];
|
|
231
|
+
if (customSamples && Array.isArray(customSamples)) {
|
|
232
|
+
customCodeSamples = customSamples
|
|
233
|
+
.filter((sample) => {
|
|
234
|
+
return (
|
|
235
|
+
typeof sample.label === 'string' &&
|
|
236
|
+
typeof sample.source === 'string' &&
|
|
237
|
+
typeof sample.lang === 'string'
|
|
238
|
+
);
|
|
239
|
+
})
|
|
240
|
+
.map((sample, index) => ({
|
|
241
|
+
key: `custom-sample-${sample.lang}-${index}`,
|
|
242
|
+
label: sample.label,
|
|
243
|
+
body: context.renderCodeBlock({
|
|
244
|
+
code: sample.source,
|
|
245
|
+
syntax: sample.lang,
|
|
246
|
+
}),
|
|
247
|
+
footer: (
|
|
248
|
+
<OpenAPICodeSampleFooter renderers={[]} data={data} context={context} />
|
|
249
|
+
),
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return customCodeSamples;
|
|
255
|
+
}
|
|
256
|
+
|
|
159
257
|
function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
|
|
160
258
|
[key: string]: string;
|
|
161
259
|
} {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useStore } from 'zustand';
|
|
5
|
+
import type { MediaTypeRenderer } from './OpenAPICodeSample';
|
|
6
|
+
import type { OpenAPIOperationData } from './types';
|
|
7
|
+
import { getOrCreateTabStoreByKey } from './useSyncedTabsGlobalState';
|
|
8
|
+
|
|
9
|
+
function useMediaTypeState(data: OpenAPIOperationData, defaultKey: string) {
|
|
10
|
+
const { method, path } = data;
|
|
11
|
+
const store = useStore(getOrCreateTabStoreByKey(`media-type-${method}-${path}`, defaultKey));
|
|
12
|
+
if (typeof store.tabKey !== 'string') {
|
|
13
|
+
throw new Error('Media type key is not a string');
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
mediaType: store.tabKey,
|
|
17
|
+
setMediaType: useCallback((index: string) => store.setTabKey(index), [store.setTabKey]),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function useMediaTypeSampleIndexState(data: OpenAPIOperationData, mediaType: string) {
|
|
22
|
+
const { method, path } = data;
|
|
23
|
+
const store = useStore(
|
|
24
|
+
getOrCreateTabStoreByKey(`media-type-sample-${mediaType}-${method}-${path}`, 0)
|
|
25
|
+
);
|
|
26
|
+
if (typeof store.tabKey !== 'number') {
|
|
27
|
+
throw new Error('Example key is not a number');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
index: store.tabKey,
|
|
31
|
+
setIndex: useCallback((index: number) => store.setTabKey(index), [store.setTabKey]),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function OpenAPIMediaTypeExamplesSelector(props: {
|
|
36
|
+
data: OpenAPIOperationData;
|
|
37
|
+
renderers: MediaTypeRenderer[];
|
|
38
|
+
}) {
|
|
39
|
+
const { data, renderers } = props;
|
|
40
|
+
if (!renderers[0]) {
|
|
41
|
+
throw new Error('No renderers provided');
|
|
42
|
+
}
|
|
43
|
+
const state = useMediaTypeState(data, renderers[0].mediaType);
|
|
44
|
+
const selected = renderers.find((r) => r.mediaType === state.mediaType) || renderers[0];
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="openapi-codesample-selectors">
|
|
48
|
+
<select
|
|
49
|
+
className={clsx('openapi-select')}
|
|
50
|
+
value={state.mediaType}
|
|
51
|
+
onChange={(e) => state.setMediaType(e.target.value)}
|
|
52
|
+
>
|
|
53
|
+
{renderers.map((renderer) => (
|
|
54
|
+
<option key={renderer.mediaType} value={renderer.mediaType}>
|
|
55
|
+
{renderer.mediaType}
|
|
56
|
+
</option>
|
|
57
|
+
))}
|
|
58
|
+
</select>
|
|
59
|
+
<ExamplesSelector data={data} renderer={selected} />
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ExamplesSelector(props: {
|
|
65
|
+
data: OpenAPIOperationData;
|
|
66
|
+
renderer: MediaTypeRenderer;
|
|
67
|
+
}) {
|
|
68
|
+
const { data, renderer } = props;
|
|
69
|
+
const state = useMediaTypeSampleIndexState(data, renderer.mediaType);
|
|
70
|
+
if (renderer.examples.length < 2) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<select
|
|
76
|
+
className={clsx('openapi-select')}
|
|
77
|
+
value={String(state.index)}
|
|
78
|
+
onChange={(e) => state.setIndex(Number(e.target.value))}
|
|
79
|
+
>
|
|
80
|
+
{renderer.examples.map((example, index) => (
|
|
81
|
+
<option key={index} value={index}>
|
|
82
|
+
{example.example.summary || `Example ${index + 1}`}
|
|
83
|
+
</option>
|
|
84
|
+
))}
|
|
85
|
+
</select>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function OpenAPIMediaTypeExamplesBody(props: {
|
|
90
|
+
data: OpenAPIOperationData;
|
|
91
|
+
renderers: MediaTypeRenderer[];
|
|
92
|
+
}) {
|
|
93
|
+
const { renderers, data } = props;
|
|
94
|
+
if (!renderers[0]) {
|
|
95
|
+
throw new Error('No renderers provided');
|
|
96
|
+
}
|
|
97
|
+
const mediaTypeState = useMediaTypeState(data, renderers[0].mediaType);
|
|
98
|
+
const selected =
|
|
99
|
+
renderers.find((r) => r.mediaType === mediaTypeState.mediaType) ?? renderers[0];
|
|
100
|
+
if (selected.examples.length === 0) {
|
|
101
|
+
return selected.element;
|
|
102
|
+
}
|
|
103
|
+
return <ExamplesBody data={data} renderer={selected} />;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function ExamplesBody(props: { data: OpenAPIOperationData; renderer: MediaTypeRenderer }) {
|
|
107
|
+
const { data, renderer } = props;
|
|
108
|
+
const exampleState = useMediaTypeSampleIndexState(data, renderer.mediaType);
|
|
109
|
+
const example = renderer.examples[exampleState.index] ?? renderer.examples[0];
|
|
110
|
+
if (!example) {
|
|
111
|
+
throw new Error(`No example found for index ${exampleState.index}`);
|
|
112
|
+
}
|
|
113
|
+
return example.element;
|
|
114
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
2
|
import type React from 'react';
|
|
3
|
+
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
3
4
|
|
|
4
5
|
interface OpenAPISchemaNameProps {
|
|
5
6
|
schema?: OpenAPIV3.SchemaObject;
|
|
@@ -31,7 +32,11 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
|
|
31
32
|
) : null}
|
|
32
33
|
</span>
|
|
33
34
|
{schema?.readOnly ? <span className="openapi-schema-readonly">read-only</span> : null}
|
|
34
|
-
{required ?
|
|
35
|
+
{required ? (
|
|
36
|
+
<span className="openapi-schema-required">required</span>
|
|
37
|
+
) : (
|
|
38
|
+
<span className="openapi-schema-optional">optional</span>
|
|
39
|
+
)}
|
|
35
40
|
{schema?.deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
|
|
36
41
|
</div>
|
|
37
42
|
);
|
|
@@ -40,17 +45,17 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
|
|
40
45
|
function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string {
|
|
41
46
|
let additionalItems = '';
|
|
42
47
|
|
|
43
|
-
if (schema.minimum || schema.minLength) {
|
|
44
|
-
additionalItems += ` · min: ${schema.minimum || schema.minLength}`;
|
|
48
|
+
if (schema.minimum || schema.minLength || schema.minItems) {
|
|
49
|
+
additionalItems += ` · min: ${schema.minimum || schema.minLength || schema.minItems}`;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
if (schema.maximum || schema.maxLength) {
|
|
48
|
-
additionalItems += ` · max: ${schema.maximum || schema.maxLength}`;
|
|
52
|
+
if (schema.maximum || schema.maxLength || schema.maxItems) {
|
|
53
|
+
additionalItems += ` · max: ${schema.maximum || schema.maxLength || schema.maxItems}`;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
// If the schema has a default value, we display it
|
|
52
57
|
if (typeof schema.default !== 'undefined') {
|
|
53
|
-
additionalItems += ` · default: ${schema.default}`;
|
|
58
|
+
additionalItems += ` · default: ${stringifyOpenAPI(schema.default)}`;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
if (schema.nullable) {
|
package/src/code-samples.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface CodeSampleInput {
|
|
|
17
17
|
body?: any;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
interface CodeSampleGenerator {
|
|
20
|
+
export interface CodeSampleGenerator {
|
|
21
21
|
id: string;
|
|
22
22
|
label: string;
|
|
23
23
|
syntax: string;
|
|
@@ -240,7 +240,7 @@ const BodyGenerators = {
|
|
|
240
240
|
body = `--data '${String(body).replace(/"/g, '')}'`;
|
|
241
241
|
} else if (isXML(contentType) || isCSV(contentType)) {
|
|
242
242
|
// We use --data-binary to avoid cURL converting newlines to \r\n
|
|
243
|
-
body = `--data-binary $'${stringifyOpenAPI(body).replace(/"/g, '')}'`;
|
|
243
|
+
body = `--data-binary $'${stringifyOpenAPI(body).replace(/"/g, '').replace(/\\n/g, '\n')}'`;
|
|
244
244
|
} else if (isGraphQL(contentType)) {
|
|
245
245
|
body = `--data '${stringifyOpenAPI(body)}'`;
|
|
246
246
|
// Set Content-Type to application/json for GraphQL, recommended by GraphQL spec
|
|
@@ -249,7 +249,7 @@ const BodyGenerators = {
|
|
|
249
249
|
// We use --data-binary to avoid cURL converting newlines to \r\n
|
|
250
250
|
body = `--data-binary '@${String(body)}'`;
|
|
251
251
|
} else {
|
|
252
|
-
body = `--data '${stringifyOpenAPI(body, null, 2)}'`;
|
|
252
|
+
body = `--data '${stringifyOpenAPI(body, null, 2).replace(/\\n/g, '\n')}'`;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
return {
|