@gitbook/react-openapi 0.2.0
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 +13 -0
- package/README.md +12 -0
- package/package.json +20 -0
- package/src/InteractiveSection.tsx +129 -0
- package/src/Markdown.tsx +12 -0
- package/src/OpenAPICodeSample.tsx +111 -0
- package/src/OpenAPIOperation.tsx +65 -0
- package/src/OpenAPIRequestBody.tsx +45 -0
- package/src/OpenAPIResponse.tsx +71 -0
- package/src/OpenAPIResponseExample.tsx +71 -0
- package/src/OpenAPIResponses.tsx +30 -0
- package/src/OpenAPISchema.test.ts +101 -0
- package/src/OpenAPISchema.tsx +401 -0
- package/src/OpenAPISecurities.tsx +71 -0
- package/src/OpenAPIServerURL.tsx +65 -0
- package/src/OpenAPIServerURLVariable.tsx +16 -0
- package/src/OpenAPISpec.tsx +118 -0
- package/src/ScalarApiButton.tsx +159 -0
- package/src/code-samples.ts +76 -0
- package/src/fetchOpenAPIOperation.test.ts +185 -0
- package/src/fetchOpenAPIOperation.ts +230 -0
- package/src/generateSchemaExample.ts +189 -0
- package/src/index.ts +3 -0
- package/src/resolveOpenAPIPath.test.ts +60 -0
- package/src/resolveOpenAPIPath.ts +145 -0
- package/src/types.ts +30 -0
- package/src/utils.ts +9 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
3
|
+
import React, { useId } from 'react';
|
|
4
|
+
|
|
5
|
+
import { InteractiveSection } from './InteractiveSection';
|
|
6
|
+
import { Markdown } from './Markdown';
|
|
7
|
+
import { SYMBOL_REF_RESOLVED } from './resolveOpenAPIPath';
|
|
8
|
+
import { OpenAPIClientContext } from './types';
|
|
9
|
+
import { noReference } from './utils';
|
|
10
|
+
|
|
11
|
+
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
|
12
|
+
|
|
13
|
+
interface OpenAPISchemaPropertyEntry {
|
|
14
|
+
propertyName?: string;
|
|
15
|
+
required?: boolean;
|
|
16
|
+
schema: OpenAPIV3.SchemaObject;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render a property of an OpenAPI schema.
|
|
21
|
+
*/
|
|
22
|
+
export function OpenAPISchemaProperty(
|
|
23
|
+
props: OpenAPISchemaPropertyEntry & {
|
|
24
|
+
/** Set of objects already observed as parents */
|
|
25
|
+
circularRefs?: CircularRefsIds;
|
|
26
|
+
context: OpenAPIClientContext;
|
|
27
|
+
className?: string;
|
|
28
|
+
},
|
|
29
|
+
) {
|
|
30
|
+
const {
|
|
31
|
+
propertyName,
|
|
32
|
+
required,
|
|
33
|
+
schema,
|
|
34
|
+
circularRefs: parentCircularRefs = new Map<OpenAPIV3.SchemaObject, string>(),
|
|
35
|
+
context,
|
|
36
|
+
className,
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
const id = useId();
|
|
40
|
+
|
|
41
|
+
const parentCircularRef = parentCircularRefs.get(schema);
|
|
42
|
+
const circularRefs = new Map(parentCircularRefs).set(schema, id);
|
|
43
|
+
|
|
44
|
+
// Avoid recursing infinitely, and instead render a link to the parent schema
|
|
45
|
+
const properties = parentCircularRef ? null : getSchemaProperties(schema);
|
|
46
|
+
const alternatives = parentCircularRef
|
|
47
|
+
? null
|
|
48
|
+
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
|
|
49
|
+
|
|
50
|
+
const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => {
|
|
51
|
+
return (
|
|
52
|
+
typeof schema.example === 'string' ||
|
|
53
|
+
typeof schema.example === 'number' ||
|
|
54
|
+
typeof schema.example === 'boolean'
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
return (
|
|
58
|
+
<InteractiveSection
|
|
59
|
+
id={id}
|
|
60
|
+
className={classNames('openapi-schema', className)}
|
|
61
|
+
toggeable={!!properties || !!alternatives}
|
|
62
|
+
defaultOpened={!!context.defaultInteractiveOpened}
|
|
63
|
+
toggleOpenIcon={context.icons.chevronRight}
|
|
64
|
+
toggleCloseIcon={context.icons.chevronDown}
|
|
65
|
+
tabs={alternatives?.[0].map((alternative, index) => ({
|
|
66
|
+
key: `${index}`,
|
|
67
|
+
label: getSchemaTitle(alternative, alternatives[1]),
|
|
68
|
+
body: circularRefs.has(alternative) ? (
|
|
69
|
+
<OpenAPISchemaCircularRef
|
|
70
|
+
id={circularRefs.get(alternative)!}
|
|
71
|
+
schema={alternative}
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<OpenAPISchemaAlternative
|
|
75
|
+
schema={alternative}
|
|
76
|
+
circularRefs={circularRefs}
|
|
77
|
+
context={context}
|
|
78
|
+
/>
|
|
79
|
+
),
|
|
80
|
+
}))}
|
|
81
|
+
header={
|
|
82
|
+
<div className={classNames('openapi-schema-presentation')}>
|
|
83
|
+
<div className={classNames('openapi-schema-name')}>
|
|
84
|
+
{propertyName ? (
|
|
85
|
+
<span className={classNames('openapi-schema-propertyname')}>
|
|
86
|
+
{propertyName}
|
|
87
|
+
</span>
|
|
88
|
+
) : null}
|
|
89
|
+
{required ? (
|
|
90
|
+
<span className={classNames('openapi-schema-required')}>*</span>
|
|
91
|
+
) : null}
|
|
92
|
+
<span className={classNames('openapi-schema-type')}>
|
|
93
|
+
{getSchemaTitle(schema)}
|
|
94
|
+
</span>
|
|
95
|
+
</div>
|
|
96
|
+
{schema.description ? (
|
|
97
|
+
<Markdown
|
|
98
|
+
source={schema.description}
|
|
99
|
+
className="openapi-schema-description"
|
|
100
|
+
/>
|
|
101
|
+
) : null}
|
|
102
|
+
{shouldDisplayExample(schema) ? (
|
|
103
|
+
<span className="openapi-schema-example">
|
|
104
|
+
Example: <code>{JSON.stringify(schema.example)}</code>
|
|
105
|
+
</span>
|
|
106
|
+
) : null}
|
|
107
|
+
</div>
|
|
108
|
+
}
|
|
109
|
+
>
|
|
110
|
+
{(properties && properties.length > 0) ||
|
|
111
|
+
(schema.enum && schema.enum.length > 0) ||
|
|
112
|
+
parentCircularRef ? (
|
|
113
|
+
<>
|
|
114
|
+
{properties?.length ? (
|
|
115
|
+
<OpenAPISchemaProperties
|
|
116
|
+
properties={properties}
|
|
117
|
+
circularRefs={circularRefs}
|
|
118
|
+
context={context}
|
|
119
|
+
/>
|
|
120
|
+
) : null}
|
|
121
|
+
{schema.enum && schema.enum.length > 0 ? (
|
|
122
|
+
<OpenAPISchemaEnum enumValues={schema.enum} />
|
|
123
|
+
) : null}
|
|
124
|
+
{parentCircularRef ? (
|
|
125
|
+
<OpenAPISchemaCircularRef id={parentCircularRef} schema={schema} />
|
|
126
|
+
) : null}
|
|
127
|
+
</>
|
|
128
|
+
) : null}
|
|
129
|
+
</InteractiveSection>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Render a set of properties of an OpenAPI schema.
|
|
135
|
+
*/
|
|
136
|
+
export function OpenAPISchemaProperties(props: {
|
|
137
|
+
id?: string;
|
|
138
|
+
properties: OpenAPISchemaPropertyEntry[];
|
|
139
|
+
circularRefs?: CircularRefsIds;
|
|
140
|
+
context: OpenAPIClientContext;
|
|
141
|
+
}) {
|
|
142
|
+
const { id, properties, circularRefs, context } = props;
|
|
143
|
+
|
|
144
|
+
if (!properties.length) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div id={id} className={classNames('openapi-schema-properties')}>
|
|
150
|
+
{properties.map((property) => (
|
|
151
|
+
<OpenAPISchemaProperty
|
|
152
|
+
key={property.propertyName}
|
|
153
|
+
circularRefs={circularRefs}
|
|
154
|
+
{...property}
|
|
155
|
+
context={context}
|
|
156
|
+
/>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Render a root schema (such as the request body or response body).
|
|
164
|
+
*/
|
|
165
|
+
export function OpenAPIRootSchema(props: {
|
|
166
|
+
schema: OpenAPIV3.SchemaObject;
|
|
167
|
+
context: OpenAPIClientContext;
|
|
168
|
+
}) {
|
|
169
|
+
const { schema, context } = props;
|
|
170
|
+
|
|
171
|
+
// Avoid recursing infinitely, and instead render a link to the parent schema
|
|
172
|
+
const properties = getSchemaProperties(schema);
|
|
173
|
+
|
|
174
|
+
if (properties && properties.length > 0) {
|
|
175
|
+
return <OpenAPISchemaProperties properties={properties} context={context} />;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<OpenAPISchemaProperty schema={schema} context={context} className="openapi-schema-root" />
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Render a tab for an alternative schema.
|
|
185
|
+
* It renders directly the properties if relevant;
|
|
186
|
+
* for primitives, it renders the schema itself.
|
|
187
|
+
*/
|
|
188
|
+
function OpenAPISchemaAlternative(props: {
|
|
189
|
+
schema: OpenAPIV3.SchemaObject;
|
|
190
|
+
circularRefs?: CircularRefsIds;
|
|
191
|
+
context: OpenAPIClientContext;
|
|
192
|
+
}) {
|
|
193
|
+
const { schema, circularRefs, context } = props;
|
|
194
|
+
const id = useId();
|
|
195
|
+
const subProperties = getSchemaProperties(schema);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<OpenAPISchemaProperties
|
|
199
|
+
id={id}
|
|
200
|
+
properties={subProperties ?? [{ schema }]}
|
|
201
|
+
circularRefs={subProperties ? new Map(circularRefs).set(schema, id) : circularRefs}
|
|
202
|
+
context={context}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Render a circular reference to a schema.
|
|
209
|
+
*/
|
|
210
|
+
function OpenAPISchemaCircularRef(props: { id: string; schema: OpenAPIV3.SchemaObject }) {
|
|
211
|
+
const { id, schema } = props;
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div className="openapi-schema-circular">
|
|
215
|
+
Circular reference to <a href={`#${id}`}>{getSchemaTitle(schema)}</a>{' '}
|
|
216
|
+
<span className="openapi-schema-circular-glyph">↩</span>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Render the enum value for a schema.
|
|
223
|
+
*/
|
|
224
|
+
export function OpenAPISchemaEnum(props: { enumValues: any[] }) {
|
|
225
|
+
const { enumValues } = props;
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div className="openapi-schema-enum">
|
|
229
|
+
{enumValues.map((value, index) => (
|
|
230
|
+
<span key={index} className="openapi-schema-enum-value">{`${value}`}</span>
|
|
231
|
+
))}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get the sub-properties of a schema.
|
|
238
|
+
*/
|
|
239
|
+
function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] {
|
|
240
|
+
if (schema.allOf) {
|
|
241
|
+
return schema.allOf.reduce((acc, subSchema) => {
|
|
242
|
+
const properties = getSchemaProperties(noReference(subSchema)) ?? [
|
|
243
|
+
{
|
|
244
|
+
schema: noReference(subSchema),
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
return [...acc, ...properties];
|
|
248
|
+
}, [] as OpenAPISchemaPropertyEntry[]);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// check array AND schema.items as this is sometimes null despite what the type indicates
|
|
252
|
+
if (schema.type === 'array' && !!schema.items) {
|
|
253
|
+
const items = noReference(schema.items);
|
|
254
|
+
const itemProperties = getSchemaProperties(items);
|
|
255
|
+
if (itemProperties) {
|
|
256
|
+
return itemProperties;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return [
|
|
260
|
+
{
|
|
261
|
+
propertyName: 'items',
|
|
262
|
+
schema: items,
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (schema.type === 'object' || schema.properties) {
|
|
268
|
+
const result: OpenAPISchemaPropertyEntry[] = [];
|
|
269
|
+
|
|
270
|
+
if (schema.properties) {
|
|
271
|
+
Object.entries(schema.properties).forEach(([propertyName, rawPropertySchema]) => {
|
|
272
|
+
const propertySchema = noReference(rawPropertySchema);
|
|
273
|
+
if (propertySchema.deprecated) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
result.push({
|
|
278
|
+
propertyName,
|
|
279
|
+
required: Array.isArray(schema.required)
|
|
280
|
+
? schema.required.includes(propertyName)
|
|
281
|
+
: undefined,
|
|
282
|
+
schema: propertySchema,
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (schema.additionalProperties) {
|
|
288
|
+
const additionalProperties = noReference(schema.additionalProperties);
|
|
289
|
+
|
|
290
|
+
result.push({
|
|
291
|
+
propertyName: 'Other properties',
|
|
292
|
+
schema: additionalProperties === true ? {} : additionalProperties,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get the alternatives to display for a schema.
|
|
304
|
+
*/
|
|
305
|
+
export function getSchemaAlternatives(
|
|
306
|
+
schema: OpenAPIV3.SchemaObject,
|
|
307
|
+
ancestors: Set<OpenAPIV3.SchemaObject> = new Set(),
|
|
308
|
+
): null | [OpenAPIV3.SchemaObject[], OpenAPIV3.DiscriminatorObject | undefined] {
|
|
309
|
+
const downAncestors = new Set(ancestors).add(schema);
|
|
310
|
+
|
|
311
|
+
if (schema.anyOf) {
|
|
312
|
+
return [
|
|
313
|
+
flattenAlternatives('anyOf', schema.anyOf.map(noReference), downAncestors),
|
|
314
|
+
noReference(schema.discriminator),
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (schema.oneOf) {
|
|
319
|
+
return [
|
|
320
|
+
flattenAlternatives('oneOf', schema.oneOf.map(noReference), downAncestors),
|
|
321
|
+
noReference(schema.discriminator),
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (schema.allOf) {
|
|
326
|
+
// allOf is managed in `getSchemaProperties`
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function flattenAlternatives(
|
|
334
|
+
alternativeType: 'oneOf' | 'allOf' | 'anyOf',
|
|
335
|
+
alternatives: OpenAPIV3.SchemaObject[],
|
|
336
|
+
ancestors: Set<OpenAPIV3.SchemaObject>,
|
|
337
|
+
): OpenAPIV3.SchemaObject[] {
|
|
338
|
+
return alternatives.reduce((acc, alternative) => {
|
|
339
|
+
if (!!alternative[alternativeType] && !ancestors.has(alternative)) {
|
|
340
|
+
return [...acc, ...(getSchemaAlternatives(alternative, ancestors)?.[0] || [])];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return [...acc, alternative];
|
|
344
|
+
}, [] as OpenAPIV3.SchemaObject[]);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function getSchemaTitle(
|
|
348
|
+
schema: OpenAPIV3.SchemaObject,
|
|
349
|
+
|
|
350
|
+
/** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */
|
|
351
|
+
discriminator?: OpenAPIV3.DiscriminatorObject,
|
|
352
|
+
): string {
|
|
353
|
+
if (schema.title) {
|
|
354
|
+
// If the schema has a title, use it
|
|
355
|
+
return schema.title;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Try using the discriminator
|
|
359
|
+
if (discriminator && schema.properties) {
|
|
360
|
+
const discriminatorProperty = noReference(schema.properties[discriminator.propertyName]);
|
|
361
|
+
if (discriminatorProperty) {
|
|
362
|
+
if (discriminatorProperty.enum) {
|
|
363
|
+
return discriminatorProperty.enum.map((value) => value.toString()).join(' | ');
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Otherwise try to infer a nice title
|
|
369
|
+
let type = 'any';
|
|
370
|
+
|
|
371
|
+
if (schema.enum) {
|
|
372
|
+
type = 'enum';
|
|
373
|
+
// check array AND schema.items as this is sometimes null despite what the type indicates
|
|
374
|
+
} else if (schema.type === 'array' && !!schema.items) {
|
|
375
|
+
type = `array of ${getSchemaTitle(noReference(schema.items))}`;
|
|
376
|
+
} else if (schema.type || schema.properties) {
|
|
377
|
+
type = schema.type ?? 'object';
|
|
378
|
+
|
|
379
|
+
if (schema.format) {
|
|
380
|
+
type += ` (${schema.format})`;
|
|
381
|
+
}
|
|
382
|
+
} else if ('anyOf' in schema) {
|
|
383
|
+
type = 'any of';
|
|
384
|
+
} else if ('oneOf' in schema) {
|
|
385
|
+
type = 'one of';
|
|
386
|
+
} else if ('allOf' in schema) {
|
|
387
|
+
type = 'all of';
|
|
388
|
+
} else if ('not' in schema) {
|
|
389
|
+
type = 'not';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (SYMBOL_REF_RESOLVED in schema) {
|
|
393
|
+
type = `${schema[SYMBOL_REF_RESOLVED]} (${type})`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (schema.nullable) {
|
|
397
|
+
type = `nullable ${type}`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return type;
|
|
401
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
import { OpenAPIClientContext } from './types';
|
|
3
|
+
import { InteractiveSection } from './InteractiveSection';
|
|
4
|
+
import { Markdown } from './Markdown';
|
|
5
|
+
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Present securities authorization that can be used for this operation.
|
|
9
|
+
*/
|
|
10
|
+
export function OpenAPISecurities(props: {
|
|
11
|
+
securities: OpenAPIOperationData['securities'];
|
|
12
|
+
context: OpenAPIClientContext;
|
|
13
|
+
}) {
|
|
14
|
+
const { securities, context } = props;
|
|
15
|
+
|
|
16
|
+
if (securities.length === 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<InteractiveSection
|
|
22
|
+
header="Authorization"
|
|
23
|
+
className="openapi-securities"
|
|
24
|
+
toggeable
|
|
25
|
+
defaultOpened={false}
|
|
26
|
+
toggleCloseIcon={context.icons.chevronDown}
|
|
27
|
+
toggleOpenIcon={context.icons.chevronRight}
|
|
28
|
+
tabs={securities.map(([key, security]) => {
|
|
29
|
+
return {
|
|
30
|
+
key: key,
|
|
31
|
+
label: key,
|
|
32
|
+
body: (
|
|
33
|
+
<>
|
|
34
|
+
<p className="openapi-securities-label">{getLabelForType(security)}</p>
|
|
35
|
+
{security.description ? (
|
|
36
|
+
<Markdown
|
|
37
|
+
source={security.description}
|
|
38
|
+
className="openapi-securities-description"
|
|
39
|
+
/>
|
|
40
|
+
) : null}
|
|
41
|
+
</>
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
})}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getLabelForType(security: OpenAPIV3.SecuritySchemeObject) {
|
|
50
|
+
switch (security.type) {
|
|
51
|
+
case 'apiKey':
|
|
52
|
+
return 'API Key';
|
|
53
|
+
case 'http':
|
|
54
|
+
if (security.scheme === 'basic') {
|
|
55
|
+
return 'Basic Auth';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (security.scheme == 'bearer') {
|
|
59
|
+
return `Bearer Token ${security.bearerFormat ? `(${security.bearerFormat})` : ''}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return 'HTTP';
|
|
63
|
+
case 'oauth2':
|
|
64
|
+
return 'OAuth2';
|
|
65
|
+
case 'openIdConnect':
|
|
66
|
+
return 'OpenID Connect';
|
|
67
|
+
default:
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
return security.type;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Show the url of the server with variables replaced by their default values.
|
|
6
|
+
*/
|
|
7
|
+
export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[] }) {
|
|
8
|
+
const { servers } = props;
|
|
9
|
+
const server = servers[0];
|
|
10
|
+
|
|
11
|
+
const parts = parseServerURL(server?.url ?? '');
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<span>
|
|
15
|
+
{parts.map((part, i) => {
|
|
16
|
+
if (part.kind === 'text') {
|
|
17
|
+
return <span key={i}>{part.text}</span>;
|
|
18
|
+
} else {
|
|
19
|
+
if (!server.variables?.[part.name]) {
|
|
20
|
+
return <span key={i}>{`{${part.name}}`}</span>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<OpenAPIServerURLVariable
|
|
25
|
+
key={i}
|
|
26
|
+
name={part.name}
|
|
27
|
+
variable={server.variables[part.name]}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
})}
|
|
32
|
+
</span>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the default URL for the server.
|
|
38
|
+
*/
|
|
39
|
+
export function getServersURL(servers: OpenAPIV3.ServerObject[]): string {
|
|
40
|
+
const server = servers[0];
|
|
41
|
+
const parts = parseServerURL(server?.url ?? '');
|
|
42
|
+
|
|
43
|
+
return parts
|
|
44
|
+
.map((part) => {
|
|
45
|
+
if (part.kind === 'text') {
|
|
46
|
+
return part.text;
|
|
47
|
+
} else {
|
|
48
|
+
return server.variables?.[part.name]?.default ?? `{${part.name}}`;
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
.join('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseServerURL(url: string) {
|
|
55
|
+
const parts = url.split(/{([^}]+)}/g);
|
|
56
|
+
const result: Array<{ kind: 'variable'; name: string } | { kind: 'text'; text: string }> = [];
|
|
57
|
+
for (let i = 0; i < parts.length; i++) {
|
|
58
|
+
if (i % 2 === 0) {
|
|
59
|
+
result.push({ kind: 'text', text: parts[i] });
|
|
60
|
+
} else {
|
|
61
|
+
result.push({ kind: 'variable', name: parts[i] });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interactive component to show the value of a server variable and let the user change it.
|
|
9
|
+
*/
|
|
10
|
+
export function OpenAPIServerURLVariable(props: {
|
|
11
|
+
name: string;
|
|
12
|
+
variable: OpenAPIV3.ServerVariableObject;
|
|
13
|
+
}) {
|
|
14
|
+
const { variable } = props;
|
|
15
|
+
return <span className={classNames('openapi-url-var')}>{variable.default}</span>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
4
|
+
|
|
5
|
+
import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation';
|
|
6
|
+
import { InteractiveSection } from './InteractiveSection';
|
|
7
|
+
import { OpenAPIRequestBody } from './OpenAPIRequestBody';
|
|
8
|
+
import { OpenAPIResponses } from './OpenAPIResponses';
|
|
9
|
+
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
|
10
|
+
import { OpenAPISecurities } from './OpenAPISecurities';
|
|
11
|
+
import { OpenAPIClientContext } from './types';
|
|
12
|
+
import { noReference } from './utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Client component to render the spec for the request and response.
|
|
16
|
+
*
|
|
17
|
+
* We use a client component as rendering recursive JSON schema in the server is expensive
|
|
18
|
+
* (the entire schema is rendered at once, while the client component only renders the visible part)
|
|
19
|
+
*/
|
|
20
|
+
export function OpenAPISpec(props: { rawData: any; context: OpenAPIClientContext }) {
|
|
21
|
+
const { rawData, context } = props;
|
|
22
|
+
|
|
23
|
+
const parsedData = fromJSON(rawData) as OpenAPIOperationData;
|
|
24
|
+
const { operation, securities } = parsedData;
|
|
25
|
+
|
|
26
|
+
const parameterGroups = groupParameters((operation.parameters || []).map(noReference));
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
{securities.length > 0 ? (
|
|
31
|
+
<OpenAPISecurities securities={securities} context={context} />
|
|
32
|
+
) : null}
|
|
33
|
+
|
|
34
|
+
{parameterGroups.map((group) => (
|
|
35
|
+
<InteractiveSection
|
|
36
|
+
key={group.key}
|
|
37
|
+
className="openapi-parameters"
|
|
38
|
+
toggeable
|
|
39
|
+
toggleOpenIcon={context.icons.chevronRight}
|
|
40
|
+
toggleCloseIcon={context.icons.chevronDown}
|
|
41
|
+
header={group.label}
|
|
42
|
+
defaultOpened={group.key === 'path' || context.defaultInteractiveOpened}
|
|
43
|
+
>
|
|
44
|
+
<OpenAPISchemaProperties
|
|
45
|
+
properties={group.parameters.map((parameter) => ({
|
|
46
|
+
propertyName: parameter.name,
|
|
47
|
+
schema: {
|
|
48
|
+
// Description of the parameter is defined at the parameter level
|
|
49
|
+
// we use display it if the schema doesn't override it
|
|
50
|
+
description: parameter.description,
|
|
51
|
+
example: parameter.example,
|
|
52
|
+
...(noReference(parameter.schema) ?? {}),
|
|
53
|
+
},
|
|
54
|
+
required: parameter.required,
|
|
55
|
+
}))}
|
|
56
|
+
context={context}
|
|
57
|
+
/>
|
|
58
|
+
</InteractiveSection>
|
|
59
|
+
))}
|
|
60
|
+
|
|
61
|
+
{operation.requestBody ? (
|
|
62
|
+
<OpenAPIRequestBody
|
|
63
|
+
requestBody={noReference(operation.requestBody)}
|
|
64
|
+
context={context}
|
|
65
|
+
/>
|
|
66
|
+
) : null}
|
|
67
|
+
{operation.responses ? (
|
|
68
|
+
<OpenAPIResponses responses={noReference(operation.responses)} context={context} />
|
|
69
|
+
) : null}
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function groupParameters(parameters: OpenAPIV3.ParameterObject[]): Array<{
|
|
75
|
+
key: string;
|
|
76
|
+
label: string;
|
|
77
|
+
parameters: OpenAPIV3.ParameterObject[];
|
|
78
|
+
}> {
|
|
79
|
+
const sorted = ['path', 'query', 'header'];
|
|
80
|
+
|
|
81
|
+
const groups: Array<{
|
|
82
|
+
key: string;
|
|
83
|
+
label: string;
|
|
84
|
+
parameters: OpenAPIV3.ParameterObject[];
|
|
85
|
+
}> = [];
|
|
86
|
+
|
|
87
|
+
parameters.forEach((parameter) => {
|
|
88
|
+
const key = parameter.in;
|
|
89
|
+
const label = getParameterGroupName(parameter.in);
|
|
90
|
+
const group = groups.find((group) => group.key === key);
|
|
91
|
+
if (group) {
|
|
92
|
+
group.parameters.push(parameter);
|
|
93
|
+
} else {
|
|
94
|
+
groups.push({
|
|
95
|
+
key,
|
|
96
|
+
label,
|
|
97
|
+
parameters: [parameter],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
groups.sort((a, b) => sorted.indexOf(a.key) - sorted.indexOf(b.key));
|
|
103
|
+
|
|
104
|
+
return groups;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getParameterGroupName(paramIn: string): string {
|
|
108
|
+
switch (paramIn) {
|
|
109
|
+
case 'path':
|
|
110
|
+
return 'Path parameters';
|
|
111
|
+
case 'query':
|
|
112
|
+
return 'Query parameters';
|
|
113
|
+
case 'header':
|
|
114
|
+
return 'Header parameters';
|
|
115
|
+
default:
|
|
116
|
+
return paramIn;
|
|
117
|
+
}
|
|
118
|
+
}
|