@gitbook/react-openapi 1.0.1 → 1.0.3
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 +28 -0
- package/dist/OpenAPICodeSample.jsx +11 -7
- package/dist/OpenAPIOperation.jsx +25 -8
- package/dist/OpenAPIResponse.jsx +16 -14
- package/dist/OpenAPIResponseExample.jsx +157 -47
- package/dist/OpenAPISchema.d.ts +2 -2
- package/dist/OpenAPISchema.jsx +50 -39
- package/dist/OpenAPISchemaName.d.ts +2 -1
- package/dist/OpenAPISchemaName.jsx +25 -4
- package/dist/OpenAPISpec.jsx +2 -26
- package/dist/OpenAPITabs.jsx +6 -2
- package/dist/code-samples.js +232 -10
- package/dist/contentTypeChecks.d.ts +9 -0
- package/dist/contentTypeChecks.js +27 -0
- package/dist/generateSchemaExample.d.ts +5 -6
- package/dist/generateSchemaExample.js +13 -8
- package/dist/json2xml.d.ts +4 -0
- package/dist/json2xml.js +7 -0
- package/dist/stringifyOpenAPI.d.ts +1 -1
- package/dist/stringifyOpenAPI.js +8 -2
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types.d.ts +18 -2
- package/dist/util/server.d.ts +9 -0
- package/dist/{OpenAPIServerURL.jsx → util/server.js} +7 -28
- package/dist/utils.d.ts +27 -3
- package/dist/utils.js +75 -3
- package/package.json +3 -2
- package/src/OpenAPICodeSample.tsx +11 -7
- package/src/OpenAPIOperation.tsx +36 -11
- package/src/OpenAPIResponse.tsx +6 -12
- package/src/OpenAPIResponseExample.tsx +237 -69
- package/src/OpenAPISchema.tsx +81 -58
- package/src/OpenAPISchemaName.tsx +37 -5
- package/src/OpenAPISpec.tsx +2 -17
- package/src/OpenAPITabs.tsx +8 -2
- package/src/__snapshots__/json2xml.test.ts.snap +18 -0
- package/src/code-samples.test.ts +594 -2
- package/src/code-samples.ts +231 -10
- package/src/contentTypeChecks.ts +35 -0
- package/src/generateSchemaExample.ts +28 -22
- package/src/json2xml.test.ts +46 -0
- package/src/json2xml.ts +8 -0
- package/src/resolveOpenAPIOperation.test.ts +1 -1
- package/src/stringifyOpenAPI.ts +13 -2
- package/src/types.ts +12 -1
- package/src/util/server.test.ts +58 -0
- package/src/util/server.ts +48 -0
- package/src/utils.ts +86 -6
- package/dist/OpenAPIServerURL.d.ts +0 -11
- package/dist/OpenAPIServerURLVariable.d.ts +0 -8
- package/dist/OpenAPIServerURLVariable.jsx +0 -8
- package/src/OpenAPIServerURL.tsx +0 -73
- package/src/OpenAPIServerURLVariable.tsx +0 -14
|
@@ -2,9 +2,10 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
|
2
2
|
import { generateSchemaExample } from './generateSchemaExample';
|
|
3
3
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
4
4
|
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
|
5
|
-
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
6
5
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
|
7
6
|
import { InteractiveSection } from './InteractiveSection';
|
|
7
|
+
import { json2xml } from './json2xml';
|
|
8
|
+
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Display an example of the response content.
|
|
@@ -38,84 +39,51 @@ export function OpenAPIResponseExample(props: {
|
|
|
38
39
|
return Number(a) - Number(b);
|
|
39
40
|
});
|
|
40
41
|
|
|
41
|
-
const
|
|
42
|
-
.map(([key,
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
const key = Object.keys(responseObject.content)[0];
|
|
49
|
-
return (
|
|
50
|
-
responseObject.content['application/json'] ??
|
|
51
|
-
(key ? responseObject.content[key] : null)
|
|
52
|
-
);
|
|
53
|
-
})();
|
|
54
|
-
|
|
55
|
-
if (!mediaTypeObject) {
|
|
42
|
+
const tabs = responses
|
|
43
|
+
.map(([key, responseObject]) => {
|
|
44
|
+
const description = resolveDescription(responseObject);
|
|
45
|
+
|
|
46
|
+
if (checkIsReference(responseObject)) {
|
|
56
47
|
return {
|
|
57
48
|
key: key,
|
|
58
49
|
label: key,
|
|
59
|
-
description
|
|
60
|
-
body:
|
|
50
|
+
description,
|
|
51
|
+
body: (
|
|
52
|
+
<OpenAPIExample
|
|
53
|
+
example={getExampleFromReference(responseObject)}
|
|
54
|
+
context={context}
|
|
55
|
+
syntax="json"
|
|
56
|
+
/>
|
|
57
|
+
),
|
|
61
58
|
};
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (firstExample) {
|
|
73
|
-
return firstExample;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (example) {
|
|
79
|
-
return { value: example };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const schema = mediaTypeObject.schema;
|
|
83
|
-
if (!schema) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return { value: generateSchemaExample(schema) };
|
|
88
|
-
})(),
|
|
89
|
-
);
|
|
61
|
+
if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
|
|
62
|
+
return {
|
|
63
|
+
key: key,
|
|
64
|
+
label: key,
|
|
65
|
+
description,
|
|
66
|
+
body: <OpenAPIEmptyResponseExample />,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
90
69
|
|
|
91
70
|
return {
|
|
92
71
|
key: key,
|
|
93
72
|
label: key,
|
|
94
73
|
description: resolveDescription(responseObject),
|
|
95
|
-
body:
|
|
96
|
-
<context.CodeBlock
|
|
97
|
-
code={
|
|
98
|
-
typeof example.value === 'string'
|
|
99
|
-
? example.value
|
|
100
|
-
: stringifyOpenAPI(example.value, null, 2)
|
|
101
|
-
}
|
|
102
|
-
syntax="json"
|
|
103
|
-
/>
|
|
104
|
-
) : (
|
|
105
|
-
<OpenAPIEmptyResponseExample />
|
|
106
|
-
),
|
|
74
|
+
body: <OpenAPIResponse context={context} content={responseObject.content} />,
|
|
107
75
|
};
|
|
108
76
|
})
|
|
109
77
|
.filter((val): val is { key: string; label: string; body: any; description: string } =>
|
|
110
78
|
Boolean(val),
|
|
111
79
|
);
|
|
112
80
|
|
|
113
|
-
if (
|
|
81
|
+
if (tabs.length === 0) {
|
|
114
82
|
return null;
|
|
115
83
|
}
|
|
116
84
|
|
|
117
85
|
return (
|
|
118
|
-
<OpenAPITabs stateKey={createStateKey('response-example')} items={
|
|
86
|
+
<OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}>
|
|
119
87
|
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
|
120
88
|
<OpenAPITabsPanels />
|
|
121
89
|
</InteractiveSection>
|
|
@@ -123,6 +91,212 @@ export function OpenAPIResponseExample(props: {
|
|
|
123
91
|
);
|
|
124
92
|
}
|
|
125
93
|
|
|
94
|
+
function OpenAPIResponse(props: {
|
|
95
|
+
context: OpenAPIContextProps;
|
|
96
|
+
content: {
|
|
97
|
+
[media: string]: OpenAPIV3.MediaTypeObject;
|
|
98
|
+
};
|
|
99
|
+
}) {
|
|
100
|
+
const { context, content } = props;
|
|
101
|
+
|
|
102
|
+
const entries = Object.entries(content);
|
|
103
|
+
const firstEntry = entries[0];
|
|
104
|
+
|
|
105
|
+
if (!firstEntry) {
|
|
106
|
+
throw new Error('One media type is required');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (entries.length === 1) {
|
|
110
|
+
const [mediaType, mediaTypeObject] = firstEntry;
|
|
111
|
+
return (
|
|
112
|
+
<OpenAPIResponseMediaType
|
|
113
|
+
context={context}
|
|
114
|
+
mediaType={mediaType}
|
|
115
|
+
mediaTypeObject={mediaTypeObject}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const tabs = entries.map((entry) => {
|
|
121
|
+
const [mediaType, mediaTypeObject] = entry;
|
|
122
|
+
return {
|
|
123
|
+
key: mediaType,
|
|
124
|
+
label: mediaType,
|
|
125
|
+
body: (
|
|
126
|
+
<OpenAPIResponseMediaType
|
|
127
|
+
context={context}
|
|
128
|
+
mediaType={mediaType}
|
|
129
|
+
mediaTypeObject={mediaTypeObject}
|
|
130
|
+
/>
|
|
131
|
+
),
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<OpenAPITabs stateKey={createStateKey('response-media-types')} items={tabs}>
|
|
137
|
+
<InteractiveSection
|
|
138
|
+
header={<OpenAPITabsList />}
|
|
139
|
+
className="openapi-response-media-types"
|
|
140
|
+
>
|
|
141
|
+
<OpenAPITabsPanels />
|
|
142
|
+
</InteractiveSection>
|
|
143
|
+
</OpenAPITabs>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function OpenAPIResponseMediaType(props: {
|
|
148
|
+
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
|
149
|
+
mediaType: string;
|
|
150
|
+
context: OpenAPIContextProps;
|
|
151
|
+
}) {
|
|
152
|
+
const { mediaTypeObject, mediaType } = props;
|
|
153
|
+
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
|
|
154
|
+
const syntax = getSyntaxFromMediaType(mediaType);
|
|
155
|
+
const firstExample = examples[0];
|
|
156
|
+
|
|
157
|
+
if (!firstExample) {
|
|
158
|
+
return <OpenAPIEmptyResponseExample />;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (examples.length === 1) {
|
|
162
|
+
return (
|
|
163
|
+
<OpenAPIExample
|
|
164
|
+
example={firstExample.example}
|
|
165
|
+
context={props.context}
|
|
166
|
+
syntax={syntax}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const tabs = examples.map((example) => {
|
|
172
|
+
return {
|
|
173
|
+
key: example.key,
|
|
174
|
+
label: example.example.summary || example.key,
|
|
175
|
+
body: (
|
|
176
|
+
<OpenAPIExample
|
|
177
|
+
example={firstExample.example}
|
|
178
|
+
context={props.context}
|
|
179
|
+
syntax={syntax}
|
|
180
|
+
/>
|
|
181
|
+
),
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<OpenAPITabs stateKey={createStateKey('response-media-type-examples')} items={tabs}>
|
|
187
|
+
<InteractiveSection
|
|
188
|
+
header={<OpenAPITabsList />}
|
|
189
|
+
className="openapi-response-media-type-examples"
|
|
190
|
+
>
|
|
191
|
+
<OpenAPITabsPanels />
|
|
192
|
+
</InteractiveSection>
|
|
193
|
+
</OpenAPITabs>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Display an example.
|
|
199
|
+
*/
|
|
200
|
+
function OpenAPIExample(props: {
|
|
201
|
+
example: OpenAPIV3.ExampleObject;
|
|
202
|
+
context: OpenAPIContextProps;
|
|
203
|
+
syntax: string;
|
|
204
|
+
}) {
|
|
205
|
+
const { example, context, syntax } = props;
|
|
206
|
+
const code = stringifyExample({ example, xml: syntax === 'xml' });
|
|
207
|
+
|
|
208
|
+
if (code === null) {
|
|
209
|
+
return <OpenAPIEmptyResponseExample />;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return context.renderCodeBlock({ code, syntax });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean }): string | null {
|
|
216
|
+
const { example, xml } = args;
|
|
217
|
+
|
|
218
|
+
if (!example.value) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (typeof example.value === 'string') {
|
|
223
|
+
return example.value;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (xml) {
|
|
227
|
+
return json2xml(example.value);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return stringifyOpenAPI(example.value, null, 2);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the syntax from a media type.
|
|
235
|
+
*/
|
|
236
|
+
function getSyntaxFromMediaType(mediaType: string): string {
|
|
237
|
+
if (mediaType.includes('json')) {
|
|
238
|
+
return 'json';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (mediaType === 'application/xml') {
|
|
242
|
+
return 'xml';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return 'text';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get examples from a media type object.
|
|
250
|
+
*/
|
|
251
|
+
function getExamplesFromMediaTypeObject(args: {
|
|
252
|
+
mediaType: string;
|
|
253
|
+
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
|
254
|
+
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
|
|
255
|
+
const { mediaTypeObject, mediaType } = args;
|
|
256
|
+
if (mediaTypeObject.examples) {
|
|
257
|
+
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
|
|
258
|
+
return {
|
|
259
|
+
key,
|
|
260
|
+
example: checkIsReference(example) ? getExampleFromReference(example) : example,
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (mediaTypeObject.example) {
|
|
266
|
+
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (mediaTypeObject.schema) {
|
|
270
|
+
if (mediaType === 'application/xml') {
|
|
271
|
+
// @TODO normally we should use the name of the schema but we don't have it
|
|
272
|
+
// fix it when we got the reference name
|
|
273
|
+
const root = mediaTypeObject.schema.xml?.name ?? 'object';
|
|
274
|
+
return [
|
|
275
|
+
{
|
|
276
|
+
key: 'default',
|
|
277
|
+
example: {
|
|
278
|
+
value: {
|
|
279
|
+
[root]: generateSchemaExample(mediaTypeObject.schema, {
|
|
280
|
+
xml: mediaType === 'application/xml',
|
|
281
|
+
}),
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
return [
|
|
288
|
+
{
|
|
289
|
+
key: 'default',
|
|
290
|
+
example: { value: generateSchemaExample(mediaTypeObject.schema) },
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Empty response example.
|
|
299
|
+
*/
|
|
126
300
|
function OpenAPIEmptyResponseExample() {
|
|
127
301
|
return (
|
|
128
302
|
<pre className="openapi-response-example-empty">
|
|
@@ -131,15 +305,9 @@ function OpenAPIEmptyResponseExample() {
|
|
|
131
305
|
);
|
|
132
306
|
}
|
|
133
307
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (isReference) {
|
|
140
|
-
// If we find a reference that wasn't resolved or needed to be resolved externally, render out the URL
|
|
141
|
-
return { value: input.value.$ref };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return input;
|
|
308
|
+
/**
|
|
309
|
+
* Generate an example from a reference object.
|
|
310
|
+
*/
|
|
311
|
+
function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
|
|
312
|
+
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
|
|
145
313
|
}
|
package/src/OpenAPISchema.tsx
CHANGED
|
@@ -13,8 +13,8 @@ import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
|
|
13
13
|
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
|
14
14
|
|
|
15
15
|
export interface OpenAPISchemaPropertyEntry {
|
|
16
|
-
propertyName?: string;
|
|
17
|
-
required?: boolean;
|
|
16
|
+
propertyName?: string | undefined;
|
|
17
|
+
required?: boolean | undefined;
|
|
18
18
|
schema: OpenAPIV3.SchemaObject;
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -47,23 +47,6 @@ export function OpenAPISchemaProperty(
|
|
|
47
47
|
? null
|
|
48
48
|
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
|
|
49
49
|
|
|
50
|
-
if ((properties && !!properties.length) || schema.type === 'object') {
|
|
51
|
-
return (
|
|
52
|
-
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
|
|
53
|
-
<OpenAPISchemaPresentation {...props} />
|
|
54
|
-
{properties && properties.length > 0 ? (
|
|
55
|
-
<OpenAPIDisclosure context={context}>
|
|
56
|
-
<OpenAPISchemaProperties
|
|
57
|
-
properties={properties}
|
|
58
|
-
circularRefs={circularRefs}
|
|
59
|
-
context={context}
|
|
60
|
-
/>
|
|
61
|
-
</OpenAPIDisclosure>
|
|
62
|
-
) : null}
|
|
63
|
-
</InteractiveSection>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
50
|
if (alternatives?.[0]?.length) {
|
|
68
51
|
return (
|
|
69
52
|
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
|
|
@@ -76,6 +59,29 @@ export function OpenAPISchemaProperty(
|
|
|
76
59
|
context={context}
|
|
77
60
|
/>
|
|
78
61
|
))}
|
|
62
|
+
{parentCircularRef ? (
|
|
63
|
+
<OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
|
|
64
|
+
) : null}
|
|
65
|
+
</InteractiveSection>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if ((properties && properties.length > 0) || schema.type === 'object') {
|
|
70
|
+
return (
|
|
71
|
+
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
|
|
72
|
+
<OpenAPISchemaPresentation {...props} />
|
|
73
|
+
{properties && properties.length > 0 ? (
|
|
74
|
+
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
|
|
75
|
+
<OpenAPISchemaProperties
|
|
76
|
+
properties={properties}
|
|
77
|
+
circularRefs={circularRefs}
|
|
78
|
+
context={context}
|
|
79
|
+
/>
|
|
80
|
+
</OpenAPIDisclosure>
|
|
81
|
+
) : null}
|
|
82
|
+
{parentCircularRef ? (
|
|
83
|
+
<OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
|
|
84
|
+
) : null}
|
|
79
85
|
</InteractiveSection>
|
|
80
86
|
);
|
|
81
87
|
}
|
|
@@ -166,16 +172,24 @@ function OpenAPISchemaAlternative(props: {
|
|
|
166
172
|
const { schema, circularRefs, context } = props;
|
|
167
173
|
const id = useId();
|
|
168
174
|
const subProperties = getSchemaProperties(schema);
|
|
175
|
+
const description = resolveDescription(schema);
|
|
169
176
|
|
|
170
177
|
return (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
<>
|
|
179
|
+
{description ? (
|
|
180
|
+
<Markdown source={description} className="openapi-schema-description" />
|
|
181
|
+
) : null}
|
|
182
|
+
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
|
|
183
|
+
<OpenAPISchemaProperties
|
|
184
|
+
id={id}
|
|
185
|
+
properties={subProperties ?? [{ schema }]}
|
|
186
|
+
circularRefs={
|
|
187
|
+
subProperties ? new Map(circularRefs).set(schema, id) : circularRefs
|
|
188
|
+
}
|
|
189
|
+
context={context}
|
|
190
|
+
/>
|
|
191
|
+
</OpenAPIDisclosure>
|
|
192
|
+
</>
|
|
179
193
|
);
|
|
180
194
|
}
|
|
181
195
|
|
|
@@ -219,7 +233,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
|
|
219
233
|
|
|
220
234
|
const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => {
|
|
221
235
|
return (
|
|
222
|
-
typeof schema.example === 'string' ||
|
|
236
|
+
(typeof schema.example === 'string' && !!schema.example) ||
|
|
223
237
|
typeof schema.example === 'number' ||
|
|
224
238
|
typeof schema.example === 'boolean' ||
|
|
225
239
|
(Array.isArray(schema.example) && schema.example.length > 0) ||
|
|
@@ -234,10 +248,10 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
|
|
234
248
|
return (
|
|
235
249
|
<div className="openapi-schema-presentation">
|
|
236
250
|
<OpenAPISchemaName
|
|
251
|
+
schema={schema}
|
|
237
252
|
type={getSchemaTitle(schema)}
|
|
238
253
|
propertyName={propertyName}
|
|
239
254
|
required={required}
|
|
240
|
-
deprecated={schema.deprecated}
|
|
241
255
|
/>
|
|
242
256
|
{schema['x-deprecated-sunset'] ? (
|
|
243
257
|
<div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
|
|
@@ -252,12 +266,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
|
|
252
266
|
) : null}
|
|
253
267
|
{shouldDisplayExample(schema) ? (
|
|
254
268
|
<div className="openapi-schema-example">
|
|
255
|
-
Example:{
|
|
256
|
-
<code>
|
|
257
|
-
{typeof schema.example === 'string'
|
|
258
|
-
? schema.example
|
|
259
|
-
: stringifyOpenAPI(schema.example)}
|
|
260
|
-
</code>
|
|
269
|
+
Example: <code>{formatExample(schema.example)}</code>
|
|
261
270
|
</div>
|
|
262
271
|
) : null}
|
|
263
272
|
{schema.pattern ? (
|
|
@@ -276,17 +285,6 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
|
|
276
285
|
* Get the sub-properties of a schema.
|
|
277
286
|
*/
|
|
278
287
|
function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] {
|
|
279
|
-
if (schema.allOf) {
|
|
280
|
-
return schema.allOf.reduce((acc, subSchema) => {
|
|
281
|
-
const properties = getSchemaProperties(subSchema) ?? [
|
|
282
|
-
{
|
|
283
|
-
schema: subSchema,
|
|
284
|
-
},
|
|
285
|
-
];
|
|
286
|
-
return [...acc, ...properties];
|
|
287
|
-
}, [] as OpenAPISchemaPropertyEntry[]);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
288
|
// check array AND schema.items as this is sometimes null despite what the type indicates
|
|
291
289
|
if (schema.type === 'array' && !!schema.items) {
|
|
292
290
|
const items = schema.items;
|
|
@@ -295,6 +293,11 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
|
|
|
295
293
|
return itemProperties;
|
|
296
294
|
}
|
|
297
295
|
|
|
296
|
+
// If the items are a primitive type, we don't need to display them
|
|
297
|
+
if (['string', 'number', 'boolean', 'integer'].includes(items.type) && !items.enum) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
298
301
|
return [
|
|
299
302
|
{
|
|
300
303
|
propertyName: 'items',
|
|
@@ -351,8 +354,7 @@ export function getSchemaAlternatives(
|
|
|
351
354
|
}
|
|
352
355
|
|
|
353
356
|
if (schema.allOf) {
|
|
354
|
-
|
|
355
|
-
return null;
|
|
357
|
+
return [flattenAlternatives('allOf', schema.allOf, downAncestors), schema.discriminator];
|
|
356
358
|
}
|
|
357
359
|
|
|
358
360
|
return null;
|
|
@@ -378,11 +380,6 @@ export function getSchemaTitle(
|
|
|
378
380
|
/** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */
|
|
379
381
|
discriminator?: OpenAPIV3.DiscriminatorObject,
|
|
380
382
|
): string {
|
|
381
|
-
if (schema.title) {
|
|
382
|
-
// If the schema has a title, use it
|
|
383
|
-
return schema.title;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
383
|
// Try using the discriminator
|
|
387
384
|
if (discriminator?.propertyName && schema.properties) {
|
|
388
385
|
const discriminatorProperty = schema.properties[discriminator.propertyName];
|
|
@@ -397,7 +394,7 @@ export function getSchemaTitle(
|
|
|
397
394
|
let type = 'any';
|
|
398
395
|
|
|
399
396
|
if (schema.enum) {
|
|
400
|
-
type =
|
|
397
|
+
type = `${schema.type} · enum`;
|
|
401
398
|
// check array AND schema.items as this is sometimes null despite what the type indicates
|
|
402
399
|
} else if (schema.type === 'array' && !!schema.items) {
|
|
403
400
|
type = `${getSchemaTitle(schema.items)}[]`;
|
|
@@ -407,7 +404,7 @@ export function getSchemaTitle(
|
|
|
407
404
|
type = schema.type ?? 'object';
|
|
408
405
|
|
|
409
406
|
if (schema.format) {
|
|
410
|
-
type += ` ${schema.format}`;
|
|
407
|
+
type += ` · ${schema.format}`;
|
|
411
408
|
}
|
|
412
409
|
} else if ('anyOf' in schema) {
|
|
413
410
|
type = 'any of';
|
|
@@ -419,9 +416,35 @@ export function getSchemaTitle(
|
|
|
419
416
|
type = 'not';
|
|
420
417
|
}
|
|
421
418
|
|
|
422
|
-
|
|
423
|
-
|
|
419
|
+
return type;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined {
|
|
423
|
+
if (schema.type === 'array' && !!schema.items) {
|
|
424
|
+
if (schema.items.oneOf) {
|
|
425
|
+
return 'available items';
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Fallback to "child attributes" for enums and objects
|
|
429
|
+
if (schema.items.enum || schema.items.type === 'object') {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
|
|
424
434
|
}
|
|
425
435
|
|
|
426
|
-
return
|
|
436
|
+
return schema.title;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function formatExample(example: any): string {
|
|
440
|
+
if (typeof example === 'string') {
|
|
441
|
+
return example
|
|
442
|
+
.replace(/\n/g, ' ') // Replace newlines with spaces
|
|
443
|
+
.replace(/\s+/g, ' ') // Collapse multiple spaces/newlines into a single space
|
|
444
|
+
.replace(/([\{\}:,])\s+/g, '$1 ') // Ensure a space after {, }, :, and ,
|
|
445
|
+
.replace(/\s+([\{\}:,])/g, ' $1') // Ensure a space before {, }, :, and ,
|
|
446
|
+
.trim();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return stringifyOpenAPI(example);
|
|
427
450
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
|
+
|
|
1
3
|
interface OpenAPISchemaNameProps {
|
|
4
|
+
schema?: OpenAPIV3.SchemaObject;
|
|
2
5
|
propertyName?: string | JSX.Element;
|
|
3
6
|
required?: boolean;
|
|
4
7
|
type?: string;
|
|
5
|
-
deprecated?: boolean;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -10,18 +12,48 @@ interface OpenAPISchemaNameProps {
|
|
|
10
12
|
* It includes the property name, type, required and deprecated status.
|
|
11
13
|
*/
|
|
12
14
|
export function OpenAPISchemaName(props: OpenAPISchemaNameProps): JSX.Element {
|
|
13
|
-
const { type, propertyName, required
|
|
15
|
+
const { schema, type, propertyName, required } = props;
|
|
16
|
+
|
|
17
|
+
const additionalItems = schema && getAdditionalItems(schema);
|
|
14
18
|
|
|
15
19
|
return (
|
|
16
20
|
<div className="openapi-schema-name">
|
|
17
21
|
{propertyName ? (
|
|
18
|
-
<span data-deprecated={deprecated} className="openapi-schema-propertyname">
|
|
22
|
+
<span data-deprecated={schema?.deprecated} className="openapi-schema-propertyname">
|
|
19
23
|
{propertyName}
|
|
20
24
|
</span>
|
|
21
25
|
) : null}
|
|
22
|
-
|
|
26
|
+
<span>
|
|
27
|
+
{type ? <span className="openapi-schema-type">{type}</span> : null}
|
|
28
|
+
{additionalItems ? (
|
|
29
|
+
<span className="openapi-schema-type">{additionalItems}</span>
|
|
30
|
+
) : null}
|
|
31
|
+
</span>
|
|
23
32
|
{required ? <span className="openapi-schema-required">required</span> : null}
|
|
24
|
-
{deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
|
|
33
|
+
{schema?.deprecated ? <span className="openapi-deprecated">Deprecated</span> : null}
|
|
25
34
|
</div>
|
|
26
35
|
);
|
|
27
36
|
}
|
|
37
|
+
|
|
38
|
+
function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string {
|
|
39
|
+
let additionalItems = '';
|
|
40
|
+
|
|
41
|
+
if (schema.minimum || schema.minLength) {
|
|
42
|
+
additionalItems += ` · min: ${schema.minimum || schema.minLength}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (schema.maximum || schema.maxLength) {
|
|
46
|
+
additionalItems += ` · max: ${schema.maximum || schema.maxLength}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If the schema has a default value, we display it
|
|
50
|
+
if (typeof schema.default !== 'undefined') {
|
|
51
|
+
additionalItems += ` · default: ${schema.default}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (schema.nullable) {
|
|
55
|
+
additionalItems = ` | nullable`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return additionalItems;
|
|
59
|
+
}
|
package/src/OpenAPISpec.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { OpenAPIResponses } from './OpenAPIResponses';
|
|
|
8
8
|
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
|
9
9
|
import { OpenAPISecurities } from './OpenAPISecurities';
|
|
10
10
|
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
|
11
|
-
import {
|
|
11
|
+
import { parameterToProperty } from './utils';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Client component to render the spec for the request and response.
|
|
@@ -38,22 +38,7 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
|
|
|
38
38
|
header={group.label}
|
|
39
39
|
>
|
|
40
40
|
<OpenAPISchemaProperties
|
|
41
|
-
properties={group.parameters.map(
|
|
42
|
-
const description = resolveDescription(parameter);
|
|
43
|
-
return {
|
|
44
|
-
propertyName: parameter.name,
|
|
45
|
-
schema: {
|
|
46
|
-
// Description of the parameter is defined at the parameter level
|
|
47
|
-
// we use display it if the schema doesn't override it
|
|
48
|
-
description: description,
|
|
49
|
-
example: parameter.example,
|
|
50
|
-
// Deprecated can be defined at the parameter level
|
|
51
|
-
deprecated: parameter.deprecated,
|
|
52
|
-
...(parameter.schema ?? {}),
|
|
53
|
-
},
|
|
54
|
-
required: parameter.required,
|
|
55
|
-
};
|
|
56
|
-
})}
|
|
41
|
+
properties={group.parameters.map(parameterToProperty)}
|
|
57
42
|
context={context}
|
|
58
43
|
/>
|
|
59
44
|
</InteractiveSection>
|