@gitbook/react-openapi 1.2.1 → 1.3.1
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 +35 -0
- package/dist/OpenAPICodeSample.jsx +7 -4
- package/dist/OpenAPIDisclosure.d.ts +1 -0
- package/dist/OpenAPIDisclosure.jsx +6 -3
- package/dist/OpenAPIDisclosureGroup.jsx +16 -15
- package/dist/OpenAPIRequestBody.jsx +7 -2
- package/dist/OpenAPIRequestBodyHeaderType.d.ts +8 -0
- package/dist/OpenAPIRequestBodyHeaderType.jsx +25 -0
- package/dist/OpenAPIResponse.d.ts +1 -1
- package/dist/OpenAPIResponse.jsx +20 -4
- package/dist/OpenAPIResponseExample.jsx +15 -3
- package/dist/OpenAPIResponses.jsx +6 -1
- package/dist/OpenAPISchema.d.ts +9 -2
- package/dist/OpenAPISchema.jsx +87 -91
- package/dist/OpenAPISchemaName.d.ts +1 -1
- package/dist/OpenAPISchemaName.jsx +5 -5
- package/dist/OpenAPISecurities.jsx +59 -1
- package/dist/OpenAPISelect.jsx +1 -0
- package/dist/OpenAPISpec.jsx +16 -1
- package/dist/OpenAPIWebhookExample.jsx +1 -1
- package/dist/StaticSection.jsx +1 -1
- package/dist/code-samples.js +6 -3
- package/dist/generateSchemaExample.js +19 -14
- package/dist/getDisclosureLabel.d.ts +7 -0
- package/dist/getDisclosureLabel.js +18 -0
- package/dist/schemas/OpenAPISchemaItem.d.ts +7 -0
- package/dist/schemas/OpenAPISchemaItem.jsx +16 -0
- package/dist/schemas/OpenAPISchemas.jsx +3 -9
- package/dist/translations/de.d.ts +7 -1
- package/dist/translations/de.js +10 -4
- package/dist/translations/en.d.ts +7 -1
- package/dist/translations/en.js +9 -3
- package/dist/translations/es.d.ts +7 -1
- package/dist/translations/es.js +10 -4
- package/dist/translations/fr.d.ts +7 -1
- package/dist/translations/fr.js +11 -5
- package/dist/translations/index.d.ts +63 -9
- package/dist/translations/ja.d.ts +7 -1
- package/dist/translations/ja.js +9 -3
- package/dist/translations/nl.d.ts +7 -1
- package/dist/translations/nl.js +9 -3
- package/dist/translations/no.d.ts +7 -1
- package/dist/translations/no.js +10 -4
- package/dist/translations/pt-br.d.ts +7 -1
- package/dist/translations/pt-br.js +10 -4
- package/dist/translations/zh.d.ts +7 -1
- package/dist/translations/zh.js +10 -4
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +38 -0
- package/package.json +2 -2
- package/src/OpenAPICodeSample.tsx +7 -6
- package/src/OpenAPIDisclosure.tsx +7 -3
- package/src/OpenAPIDisclosureGroup.tsx +49 -48
- package/src/OpenAPIRequestBody.tsx +11 -2
- package/src/OpenAPIRequestBodyHeaderType.tsx +36 -0
- package/src/OpenAPIResponse.tsx +37 -5
- package/src/OpenAPIResponseExample.tsx +46 -35
- package/src/OpenAPIResponses.tsx +4 -4
- package/src/OpenAPISchema.tsx +157 -130
- package/src/OpenAPISchemaName.tsx +10 -8
- package/src/OpenAPISecurities.tsx +111 -7
- package/src/OpenAPISelect.tsx +1 -1
- package/src/OpenAPISpec.tsx +21 -1
- package/src/OpenAPIWebhookExample.tsx +2 -2
- package/src/StaticSection.tsx +1 -1
- package/src/code-samples.test.ts +3 -2
- package/src/code-samples.ts +19 -12
- package/src/generateSchemaExample.test.ts +20 -0
- package/src/generateSchemaExample.ts +9 -1
- package/src/getDisclosureLabel.ts +25 -0
- package/src/schemas/OpenAPISchemaItem.tsx +34 -0
- package/src/schemas/OpenAPISchemas.tsx +7 -13
- package/src/translations/de.ts +10 -4
- package/src/translations/en.ts +9 -3
- package/src/translations/es.ts +10 -4
- package/src/translations/fr.ts +11 -5
- package/src/translations/ja.ts +9 -3
- package/src/translations/nl.ts +9 -3
- package/src/translations/no.ts +10 -4
- package/src/translations/pt-br.ts +10 -4
- package/src/translations/zh.ts +10 -4
- package/src/utils.ts +37 -0
package/src/OpenAPISchema.tsx
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
6
6
|
import { useId } from 'react';
|
|
7
|
+
import type { ComponentPropsWithoutRef } from 'react';
|
|
7
8
|
|
|
8
9
|
import clsx from 'clsx';
|
|
9
10
|
import { Markdown } from './Markdown';
|
|
@@ -12,80 +13,105 @@ import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
|
|
12
13
|
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
|
13
14
|
import type { OpenAPIClientContext } from './context';
|
|
14
15
|
import { retrocycle } from './decycle';
|
|
16
|
+
import { getDisclosureLabel } from './getDisclosureLabel';
|
|
15
17
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
16
18
|
import { tString } from './translate';
|
|
17
|
-
import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
|
|
19
|
+
import { checkIsReference, getSchemaTitle, resolveDescription, resolveFirstExample } from './utils';
|
|
18
20
|
|
|
19
21
|
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
|
|
20
22
|
|
|
21
23
|
export interface OpenAPISchemaPropertyEntry {
|
|
22
|
-
propertyName?: string
|
|
23
|
-
required?: boolean |
|
|
24
|
+
propertyName?: string;
|
|
25
|
+
required?: boolean | null;
|
|
24
26
|
schema: OpenAPIV3.SchemaObject;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Render a property of an OpenAPI schema.
|
|
29
31
|
*/
|
|
30
|
-
function OpenAPISchemaProperty(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
function OpenAPISchemaProperty(
|
|
33
|
+
props: {
|
|
34
|
+
property: OpenAPISchemaPropertyEntry;
|
|
35
|
+
context: OpenAPIClientContext;
|
|
36
|
+
circularRefs: CircularRefsIds;
|
|
37
|
+
className?: string;
|
|
38
|
+
} & Omit<ComponentPropsWithoutRef<'div'>, 'property' | 'context' | 'circularRefs' | 'className'>
|
|
39
|
+
) {
|
|
40
|
+
const { circularRefs: parentCircularRefs, context, className, property, ...rest } = props;
|
|
37
41
|
|
|
38
42
|
const { schema } = property;
|
|
39
43
|
|
|
40
44
|
const id = useId();
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const circularRefId = parentCircularRefs.get(schema);
|
|
47
|
+
// Avoid recursing infinitely, and instead render a link to the parent schema
|
|
48
|
+
if (circularRefId) {
|
|
49
|
+
return <OpenAPISchemaCircularRef id={circularRefId} schema={schema} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const circularRefs = new Map(parentCircularRefs);
|
|
53
|
+
circularRefs.set(schema, id);
|
|
54
|
+
|
|
55
|
+
const properties = getSchemaProperties(schema);
|
|
56
|
+
|
|
57
|
+
const ancestors = new Set(circularRefs.keys());
|
|
58
|
+
const alternatives = getSchemaAlternatives(schema, ancestors);
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
const header = <OpenAPISchemaPresentation context={context} property={property} />;
|
|
61
|
+
const content = (() => {
|
|
62
|
+
if (properties?.length) {
|
|
63
|
+
return (
|
|
64
|
+
<OpenAPISchemaProperties
|
|
65
|
+
properties={properties}
|
|
66
|
+
circularRefs={circularRefs}
|
|
67
|
+
context={context}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (alternatives) {
|
|
73
|
+
return (
|
|
74
|
+
<div className="openapi-schema-alternatives">
|
|
75
|
+
{alternatives.map((alternativeSchema, index) => (
|
|
76
|
+
<div key={index} className="openapi-schema-alternative">
|
|
77
|
+
<OpenAPISchemaAlternative
|
|
78
|
+
schema={alternativeSchema}
|
|
66
79
|
circularRefs={circularRefs}
|
|
67
80
|
context={context}
|
|
68
81
|
/>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
{index < alternatives.length - 1 ? (
|
|
83
|
+
<OpenAPISchemaAlternativeSeparator
|
|
84
|
+
schema={schema}
|
|
85
|
+
context={context}
|
|
86
|
+
/>
|
|
87
|
+
) : null}
|
|
88
|
+
</div>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
94
|
+
return null;
|
|
95
|
+
})();
|
|
96
|
+
|
|
97
|
+
if (properties?.length) {
|
|
98
|
+
return (
|
|
99
|
+
<OpenAPIDisclosure
|
|
100
|
+
icon={context.icons.plus}
|
|
101
|
+
className={clsx('openapi-schema', className)}
|
|
102
|
+
header={header}
|
|
103
|
+
label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })}
|
|
104
|
+
{...rest}
|
|
105
|
+
>
|
|
106
|
+
{content}
|
|
107
|
+
</OpenAPIDisclosure>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
86
110
|
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
return (
|
|
112
|
+
<div id={id} {...rest} className={clsx('openapi-schema', className)}>
|
|
113
|
+
{header}
|
|
114
|
+
{content}
|
|
89
115
|
</div>
|
|
90
116
|
);
|
|
91
117
|
}
|
|
@@ -115,6 +141,7 @@ function OpenAPISchemaProperties(props: {
|
|
|
115
141
|
circularRefs={circularRefs}
|
|
116
142
|
property={property}
|
|
117
143
|
context={context}
|
|
144
|
+
style={{ animationDelay: `${index * 0.02}s` }}
|
|
118
145
|
/>
|
|
119
146
|
);
|
|
120
147
|
})}
|
|
@@ -205,34 +232,48 @@ function OpenAPISchemaAlternative(props: {
|
|
|
205
232
|
context: OpenAPIClientContext;
|
|
206
233
|
}) {
|
|
207
234
|
const { schema, circularRefs, context } = props;
|
|
208
|
-
|
|
209
|
-
const description = resolveDescription(schema);
|
|
210
235
|
const properties = getSchemaProperties(schema);
|
|
211
236
|
|
|
237
|
+
return properties?.length ? (
|
|
238
|
+
<OpenAPIDisclosure
|
|
239
|
+
icon={context.icons.plus}
|
|
240
|
+
header={<OpenAPISchemaPresentation property={{ schema }} context={context} />}
|
|
241
|
+
label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })}
|
|
242
|
+
>
|
|
243
|
+
<OpenAPISchemaProperties
|
|
244
|
+
properties={properties}
|
|
245
|
+
circularRefs={circularRefs}
|
|
246
|
+
context={context}
|
|
247
|
+
/>
|
|
248
|
+
</OpenAPIDisclosure>
|
|
249
|
+
) : (
|
|
250
|
+
<OpenAPISchemaProperty
|
|
251
|
+
property={{ schema }}
|
|
252
|
+
circularRefs={circularRefs}
|
|
253
|
+
context={context}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function OpenAPISchemaAlternativeSeparator(props: {
|
|
259
|
+
schema: OpenAPIV3.SchemaObject;
|
|
260
|
+
context: OpenAPIClientContext;
|
|
261
|
+
}) {
|
|
262
|
+
const { schema, context } = props;
|
|
263
|
+
|
|
264
|
+
const anyOf = schema.anyOf || schema.items?.anyOf;
|
|
265
|
+
const oneOf = schema.oneOf || schema.items?.oneOf;
|
|
266
|
+
const allOf = schema.allOf || schema.items?.allOf;
|
|
267
|
+
|
|
268
|
+
if (!anyOf && !oneOf && !allOf) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
212
272
|
return (
|
|
213
|
-
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
<OpenAPIDisclosure
|
|
218
|
-
icon={context.icons.plus}
|
|
219
|
-
label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })}
|
|
220
|
-
>
|
|
221
|
-
{properties?.length ? (
|
|
222
|
-
<OpenAPISchemaProperties
|
|
223
|
-
properties={properties}
|
|
224
|
-
circularRefs={circularRefs}
|
|
225
|
-
context={context}
|
|
226
|
-
/>
|
|
227
|
-
) : (
|
|
228
|
-
<OpenAPISchemaProperty
|
|
229
|
-
property={{ schema }}
|
|
230
|
-
circularRefs={circularRefs}
|
|
231
|
-
context={context}
|
|
232
|
-
/>
|
|
233
|
-
)}
|
|
234
|
-
</OpenAPIDisclosure>
|
|
235
|
-
</>
|
|
273
|
+
<span className="openapi-schema-alternative-separator">
|
|
274
|
+
{(anyOf || oneOf) && tString(context.translation, 'or')}
|
|
275
|
+
{allOf && tString(context.translation, 'and')}
|
|
276
|
+
</span>
|
|
236
277
|
);
|
|
237
278
|
}
|
|
238
279
|
|
|
@@ -293,7 +334,7 @@ function OpenAPISchemaEnum(props: {
|
|
|
293
334
|
|
|
294
335
|
return (
|
|
295
336
|
<span className="openapi-schema-enum">
|
|
296
|
-
|
|
337
|
+
{tString(context.translation, 'possible_values')}:{' '}
|
|
297
338
|
{enumValues.map((item, index) => (
|
|
298
339
|
<span key={index} className="openapi-schema-enum-value">
|
|
299
340
|
<OpenAPICopyButton
|
|
@@ -313,7 +354,7 @@ function OpenAPISchemaEnum(props: {
|
|
|
313
354
|
/**
|
|
314
355
|
* Render the top row of a schema. e.g: name, type, and required status.
|
|
315
356
|
*/
|
|
316
|
-
function OpenAPISchemaPresentation(props: {
|
|
357
|
+
export function OpenAPISchemaPresentation(props: {
|
|
317
358
|
property: OpenAPISchemaPropertyEntry;
|
|
318
359
|
context: OpenAPIClientContext;
|
|
319
360
|
}) {
|
|
@@ -437,6 +478,14 @@ export function getSchemaAlternatives(
|
|
|
437
478
|
schema: OpenAPIV3.SchemaObject,
|
|
438
479
|
ancestors: Set<OpenAPIV3.SchemaObject> = new Set()
|
|
439
480
|
): OpenAPIV3.SchemaObject[] | null {
|
|
481
|
+
// Search for alternatives in the items property if it exists
|
|
482
|
+
if (
|
|
483
|
+
schema.items &&
|
|
484
|
+
('oneOf' in schema.items || 'allOf' in schema.items || 'anyOf' in schema.items)
|
|
485
|
+
) {
|
|
486
|
+
return getSchemaAlternatives(schema.items, ancestors);
|
|
487
|
+
}
|
|
488
|
+
|
|
440
489
|
const alternatives:
|
|
441
490
|
| [AlternativeType, (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[]]
|
|
442
491
|
| null = (() => {
|
|
@@ -552,6 +601,9 @@ function flattenAlternatives(
|
|
|
552
601
|
schemasOrRefs: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[],
|
|
553
602
|
ancestors: Set<OpenAPIV3.SchemaObject>
|
|
554
603
|
): OpenAPIV3.SchemaObject[] {
|
|
604
|
+
// Get the parent schema's required fields from the most recent ancestor
|
|
605
|
+
const latestAncestor = Array.from(ancestors).pop();
|
|
606
|
+
|
|
555
607
|
return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
|
|
556
608
|
if (checkIsReference(schemaOrRef)) {
|
|
557
609
|
return acc;
|
|
@@ -560,68 +612,43 @@ function flattenAlternatives(
|
|
|
560
612
|
if (schemaOrRef[alternativeType] && !ancestors.has(schemaOrRef)) {
|
|
561
613
|
const schemas = getSchemaAlternatives(schemaOrRef, ancestors);
|
|
562
614
|
if (schemas) {
|
|
563
|
-
acc.push(
|
|
615
|
+
acc.push(
|
|
616
|
+
...schemas.map((schema) => ({
|
|
617
|
+
...schema,
|
|
618
|
+
required: mergeRequiredFields(schema, latestAncestor),
|
|
619
|
+
}))
|
|
620
|
+
);
|
|
564
621
|
}
|
|
565
622
|
return acc;
|
|
566
623
|
}
|
|
567
624
|
|
|
568
|
-
|
|
625
|
+
// For direct schemas, handle required fields
|
|
626
|
+
const schema = {
|
|
627
|
+
...schemaOrRef,
|
|
628
|
+
required: mergeRequiredFields(schemaOrRef, latestAncestor),
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
acc.push(schema);
|
|
569
632
|
return acc;
|
|
570
633
|
}, []);
|
|
571
634
|
}
|
|
572
635
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
} else if (Array.isArray(schema.type)) {
|
|
583
|
-
type = schema.type.join(' | ');
|
|
584
|
-
} else if (schema.type || schema.properties) {
|
|
585
|
-
type = schema.type ?? 'object';
|
|
586
|
-
|
|
587
|
-
if (schema.format) {
|
|
588
|
-
type += ` · ${schema.format}`;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
if ('anyOf' in schema) {
|
|
593
|
-
type = 'any of';
|
|
594
|
-
} else if ('oneOf' in schema) {
|
|
595
|
-
type = 'one of';
|
|
596
|
-
} else if ('allOf' in schema) {
|
|
597
|
-
type = 'all of';
|
|
598
|
-
} else if ('not' in schema) {
|
|
599
|
-
type = 'not';
|
|
636
|
+
/**
|
|
637
|
+
* Merge the required fields of a schema with the required fields of its latest ancestor.
|
|
638
|
+
*/
|
|
639
|
+
function mergeRequiredFields(
|
|
640
|
+
schemaOrRef: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
|
|
641
|
+
latestAncestor: OpenAPIV3.SchemaObject | undefined
|
|
642
|
+
) {
|
|
643
|
+
if (!schemaOrRef.required && !latestAncestor?.required) {
|
|
644
|
+
return undefined;
|
|
600
645
|
}
|
|
601
646
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
function getDisclosureLabel(props: {
|
|
606
|
-
schema: OpenAPIV3.SchemaObject;
|
|
607
|
-
isExpanded: boolean;
|
|
608
|
-
context: OpenAPIClientContext;
|
|
609
|
-
}) {
|
|
610
|
-
const { schema, isExpanded, context } = props;
|
|
611
|
-
let label: string;
|
|
612
|
-
if (schema.type === 'array' && !!schema.items) {
|
|
613
|
-
if (schema.items.oneOf) {
|
|
614
|
-
label = tString(context.translation, 'available_items').toLowerCase();
|
|
615
|
-
}
|
|
616
|
-
// Fallback to "child attributes" for enums and objects
|
|
617
|
-
else if (schema.items.enum || schema.items.type === 'object') {
|
|
618
|
-
label = tString(context.translation, 'child_attributes').toLowerCase();
|
|
619
|
-
} else {
|
|
620
|
-
label = schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
|
|
621
|
-
}
|
|
622
|
-
} else {
|
|
623
|
-
label = schema.title || tString(context.translation, 'child_attributes').toLowerCase();
|
|
647
|
+
if (checkIsReference(schemaOrRef)) {
|
|
648
|
+
return latestAncestor?.required;
|
|
624
649
|
}
|
|
625
650
|
|
|
626
|
-
return
|
|
651
|
+
return Array.from(
|
|
652
|
+
new Set([...(latestAncestor?.required || []), ...(schemaOrRef.required || [])])
|
|
653
|
+
);
|
|
627
654
|
}
|
|
@@ -6,7 +6,7 @@ import { t, tString } from './translate';
|
|
|
6
6
|
interface OpenAPISchemaNameProps {
|
|
7
7
|
schema?: OpenAPIV3.SchemaObject;
|
|
8
8
|
propertyName?: string | React.JSX.Element;
|
|
9
|
-
required?: boolean;
|
|
9
|
+
required?: boolean | null;
|
|
10
10
|
type?: string;
|
|
11
11
|
context: OpenAPIClientContext;
|
|
12
12
|
}
|
|
@@ -27,12 +27,14 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
|
|
27
27
|
{propertyName}
|
|
28
28
|
</span>
|
|
29
29
|
) : null}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
{type || additionalItems ? (
|
|
31
|
+
<span>
|
|
32
|
+
{type ? <span className="openapi-schema-type">{type}</span> : null}
|
|
33
|
+
{additionalItems ? (
|
|
34
|
+
<span className="openapi-schema-type">{additionalItems}</span>
|
|
35
|
+
) : null}
|
|
36
|
+
</span>
|
|
37
|
+
) : null}
|
|
36
38
|
{schema?.readOnly ? (
|
|
37
39
|
<span className="openapi-schema-readonly">
|
|
38
40
|
{t(context.translation, 'read_only')}
|
|
@@ -43,7 +45,7 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
|
|
|
43
45
|
{t(context.translation, 'write_only')}
|
|
44
46
|
</span>
|
|
45
47
|
) : null}
|
|
46
|
-
{required ? (
|
|
48
|
+
{required === null ? null : required ? (
|
|
47
49
|
<span className="openapi-schema-required">
|
|
48
50
|
{t(context.translation, 'required')}
|
|
49
51
|
</span>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
1
2
|
import { InteractiveSection } from './InteractiveSection';
|
|
2
3
|
import { Markdown } from './Markdown';
|
|
4
|
+
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
|
3
5
|
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
|
4
6
|
import type { OpenAPIClientContext } from './context';
|
|
5
7
|
import { t } from './translate';
|
|
@@ -105,13 +107,7 @@ function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPI
|
|
|
105
107
|
/>
|
|
106
108
|
);
|
|
107
109
|
case 'oauth2':
|
|
108
|
-
return
|
|
109
|
-
<OpenAPISchemaName
|
|
110
|
-
context={context}
|
|
111
|
-
propertyName="OAuth2"
|
|
112
|
-
required={security.required}
|
|
113
|
-
/>
|
|
114
|
-
);
|
|
110
|
+
return <OpenAPISchemaOAuth2Flows context={context} security={security} />;
|
|
115
111
|
case 'openIdConnect':
|
|
116
112
|
return (
|
|
117
113
|
<OpenAPISchemaName
|
|
@@ -125,3 +121,111 @@ function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPI
|
|
|
125
121
|
return security.type;
|
|
126
122
|
}
|
|
127
123
|
}
|
|
124
|
+
|
|
125
|
+
function OpenAPISchemaOAuth2Flows(props: {
|
|
126
|
+
context: OpenAPIClientContext;
|
|
127
|
+
security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean };
|
|
128
|
+
}) {
|
|
129
|
+
const { context, security } = props;
|
|
130
|
+
|
|
131
|
+
const flows = Object.entries(security.flows ?? {});
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className="openapi-securities-oauth-flows">
|
|
135
|
+
{flows.map(([name, flow], index) => (
|
|
136
|
+
<OpenAPISchemaOAuth2Item
|
|
137
|
+
key={index}
|
|
138
|
+
flow={flow}
|
|
139
|
+
name={name}
|
|
140
|
+
context={context}
|
|
141
|
+
security={security}
|
|
142
|
+
/>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function OpenAPISchemaOAuth2Item(props: {
|
|
149
|
+
flow: NonNullable<OpenAPIV3.OAuth2SecurityScheme['flows']>[keyof NonNullable<
|
|
150
|
+
OpenAPIV3.OAuth2SecurityScheme['flows']
|
|
151
|
+
>];
|
|
152
|
+
name: string;
|
|
153
|
+
context: OpenAPIClientContext;
|
|
154
|
+
security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean };
|
|
155
|
+
}) {
|
|
156
|
+
const { flow, context, security, name } = props;
|
|
157
|
+
|
|
158
|
+
if (!flow) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const scopes = Object.entries(flow?.scopes ?? {});
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div>
|
|
166
|
+
<OpenAPISchemaName
|
|
167
|
+
context={context}
|
|
168
|
+
propertyName="OAuth2"
|
|
169
|
+
type={name}
|
|
170
|
+
required={security.required}
|
|
171
|
+
/>
|
|
172
|
+
<div className="openapi-securities-oauth-content openapi-markdown">
|
|
173
|
+
{security.description ? <Markdown source={security.description} /> : null}
|
|
174
|
+
{'authorizationUrl' in flow && flow.authorizationUrl ? (
|
|
175
|
+
<span>
|
|
176
|
+
Authorization URL:{' '}
|
|
177
|
+
<OpenAPICopyButton
|
|
178
|
+
value={flow.authorizationUrl}
|
|
179
|
+
context={context}
|
|
180
|
+
className="openapi-securities-url"
|
|
181
|
+
withTooltip
|
|
182
|
+
>
|
|
183
|
+
{flow.authorizationUrl}
|
|
184
|
+
</OpenAPICopyButton>
|
|
185
|
+
</span>
|
|
186
|
+
) : null}
|
|
187
|
+
{'tokenUrl' in flow && flow.tokenUrl ? (
|
|
188
|
+
<span>
|
|
189
|
+
Token URL:{' '}
|
|
190
|
+
<OpenAPICopyButton
|
|
191
|
+
value={flow.tokenUrl}
|
|
192
|
+
context={context}
|
|
193
|
+
className="openapi-securities-url"
|
|
194
|
+
withTooltip
|
|
195
|
+
>
|
|
196
|
+
{flow.tokenUrl}
|
|
197
|
+
</OpenAPICopyButton>
|
|
198
|
+
</span>
|
|
199
|
+
) : null}
|
|
200
|
+
{'refreshUrl' in flow && flow.refreshUrl ? (
|
|
201
|
+
<span>
|
|
202
|
+
Refresh URL:{' '}
|
|
203
|
+
<OpenAPICopyButton
|
|
204
|
+
value={flow.refreshUrl}
|
|
205
|
+
context={context}
|
|
206
|
+
className="openapi-securities-url"
|
|
207
|
+
withTooltip
|
|
208
|
+
>
|
|
209
|
+
{flow.refreshUrl}
|
|
210
|
+
</OpenAPICopyButton>
|
|
211
|
+
</span>
|
|
212
|
+
) : null}
|
|
213
|
+
{scopes.length ? (
|
|
214
|
+
<div>
|
|
215
|
+
{t(context.translation, 'available_scopes')}:{' '}
|
|
216
|
+
<ul>
|
|
217
|
+
{scopes.map(([key, value]) => (
|
|
218
|
+
<li key={key}>
|
|
219
|
+
<OpenAPICopyButton value={key} context={context} withTooltip>
|
|
220
|
+
<code>{key}</code>
|
|
221
|
+
</OpenAPICopyButton>
|
|
222
|
+
: {value}
|
|
223
|
+
</li>
|
|
224
|
+
))}
|
|
225
|
+
</ul>
|
|
226
|
+
</div>
|
|
227
|
+
) : null}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
package/src/OpenAPISelect.tsx
CHANGED
|
@@ -33,7 +33,7 @@ interface OpenAPISelectProps<T extends OpenAPISelectItem> extends Omit<SelectPro
|
|
|
33
33
|
icon?: React.ReactNode;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export function useSelectState(stateKey = 'select-state', initialKey
|
|
36
|
+
export function useSelectState(stateKey = 'select-state', initialKey: Key = 'default') {
|
|
37
37
|
const store = useStore(getOrCreateStoreByKey(stateKey, initialKey));
|
|
38
38
|
return {
|
|
39
39
|
key: store.key,
|
package/src/OpenAPISpec.tsx
CHANGED
|
@@ -18,7 +18,7 @@ export function OpenAPISpec(props: {
|
|
|
18
18
|
|
|
19
19
|
const { operation } = data;
|
|
20
20
|
|
|
21
|
-
const parameters = operation.parameters ?? [];
|
|
21
|
+
const parameters = deduplicateParameters(operation.parameters ?? []);
|
|
22
22
|
const parameterGroups = groupParameters(parameters, context);
|
|
23
23
|
|
|
24
24
|
const securities = 'securities' in data ? data.securities : [];
|
|
@@ -113,3 +113,23 @@ function getParameterGroupName(paramIn: string, context: OpenAPIClientContext):
|
|
|
113
113
|
return paramIn;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
/** Deduplicate parameters by name and in.
|
|
118
|
+
* Some specs have both parameters define at path and operation level.
|
|
119
|
+
* We only want to display one of them.
|
|
120
|
+
*/
|
|
121
|
+
function deduplicateParameters(parameters: OpenAPI.Parameters): OpenAPI.Parameters {
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
|
|
124
|
+
return parameters.filter((param) => {
|
|
125
|
+
const key = `${param.name}:${param.in}`;
|
|
126
|
+
|
|
127
|
+
if (seen.has(key)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
seen.add(key);
|
|
132
|
+
|
|
133
|
+
return true;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -19,9 +19,9 @@ export function OpenAPIWebhookExample(props: {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
return Object.entries(
|
|
22
|
-
operation.requestBody.content as Record<string, OpenAPIV3.MediaTypeObject>
|
|
22
|
+
operation.requestBody.content as Record<string, OpenAPIV3.MediaTypeObject | null>
|
|
23
23
|
).map(([key, value]) => {
|
|
24
|
-
const schema = value
|
|
24
|
+
const schema = value?.schema;
|
|
25
25
|
|
|
26
26
|
if (!schema) {
|
|
27
27
|
return {
|
package/src/StaticSection.tsx
CHANGED
|
@@ -11,7 +11,7 @@ export function SectionHeader(props: ComponentPropsWithoutRef<'div'>) {
|
|
|
11
11
|
{...props}
|
|
12
12
|
className={clsx(
|
|
13
13
|
'openapi-section-header',
|
|
14
|
-
props.className
|
|
14
|
+
props.className ? `${props.className}-header` : undefined
|
|
15
15
|
)}
|
|
16
16
|
/>
|
|
17
17
|
);
|
package/src/code-samples.test.ts
CHANGED
|
@@ -400,7 +400,7 @@ describe('python code sample generator', () => {
|
|
|
400
400
|
const output = generator?.generate(input);
|
|
401
401
|
|
|
402
402
|
expect(output).toBe(
|
|
403
|
-
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/x-www-form-urlencoded"},\n data={"key":"value"}\n)\n\ndata = response.json()'
|
|
403
|
+
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/x-www-form-urlencoded"},\n data={\n "key": "value"\n }\n)\n\ndata = response.json()'
|
|
404
404
|
);
|
|
405
405
|
});
|
|
406
406
|
|
|
@@ -415,13 +415,14 @@ describe('python code sample generator', () => {
|
|
|
415
415
|
key: 'value',
|
|
416
416
|
truethy: true,
|
|
417
417
|
falsey: false,
|
|
418
|
+
nullish: null,
|
|
418
419
|
},
|
|
419
420
|
};
|
|
420
421
|
|
|
421
422
|
const output = generator?.generate(input);
|
|
422
423
|
|
|
423
424
|
expect(output).toBe(
|
|
424
|
-
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/json"},\n data=json.dumps({"key":"value"
|
|
425
|
+
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/json"},\n data=json.dumps({\n "key": "value",\n "truethy": True,\n "falsey": False,\n "nullish": None\n })\n)\n\ndata = response.json()'
|
|
425
426
|
);
|
|
426
427
|
});
|
|
427
428
|
|