@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.
Files changed (83) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/OpenAPICodeSample.jsx +7 -4
  3. package/dist/OpenAPIDisclosure.d.ts +1 -0
  4. package/dist/OpenAPIDisclosure.jsx +6 -3
  5. package/dist/OpenAPIDisclosureGroup.jsx +16 -15
  6. package/dist/OpenAPIRequestBody.jsx +7 -2
  7. package/dist/OpenAPIRequestBodyHeaderType.d.ts +8 -0
  8. package/dist/OpenAPIRequestBodyHeaderType.jsx +25 -0
  9. package/dist/OpenAPIResponse.d.ts +1 -1
  10. package/dist/OpenAPIResponse.jsx +20 -4
  11. package/dist/OpenAPIResponseExample.jsx +15 -3
  12. package/dist/OpenAPIResponses.jsx +6 -1
  13. package/dist/OpenAPISchema.d.ts +9 -2
  14. package/dist/OpenAPISchema.jsx +87 -91
  15. package/dist/OpenAPISchemaName.d.ts +1 -1
  16. package/dist/OpenAPISchemaName.jsx +5 -5
  17. package/dist/OpenAPISecurities.jsx +59 -1
  18. package/dist/OpenAPISelect.jsx +1 -0
  19. package/dist/OpenAPISpec.jsx +16 -1
  20. package/dist/OpenAPIWebhookExample.jsx +1 -1
  21. package/dist/StaticSection.jsx +1 -1
  22. package/dist/code-samples.js +6 -3
  23. package/dist/generateSchemaExample.js +19 -14
  24. package/dist/getDisclosureLabel.d.ts +7 -0
  25. package/dist/getDisclosureLabel.js +18 -0
  26. package/dist/schemas/OpenAPISchemaItem.d.ts +7 -0
  27. package/dist/schemas/OpenAPISchemaItem.jsx +16 -0
  28. package/dist/schemas/OpenAPISchemas.jsx +3 -9
  29. package/dist/translations/de.d.ts +7 -1
  30. package/dist/translations/de.js +10 -4
  31. package/dist/translations/en.d.ts +7 -1
  32. package/dist/translations/en.js +9 -3
  33. package/dist/translations/es.d.ts +7 -1
  34. package/dist/translations/es.js +10 -4
  35. package/dist/translations/fr.d.ts +7 -1
  36. package/dist/translations/fr.js +11 -5
  37. package/dist/translations/index.d.ts +63 -9
  38. package/dist/translations/ja.d.ts +7 -1
  39. package/dist/translations/ja.js +9 -3
  40. package/dist/translations/nl.d.ts +7 -1
  41. package/dist/translations/nl.js +9 -3
  42. package/dist/translations/no.d.ts +7 -1
  43. package/dist/translations/no.js +10 -4
  44. package/dist/translations/pt-br.d.ts +7 -1
  45. package/dist/translations/pt-br.js +10 -4
  46. package/dist/translations/zh.d.ts +7 -1
  47. package/dist/translations/zh.js +10 -4
  48. package/dist/tsconfig.build.tsbuildinfo +1 -1
  49. package/dist/utils.d.ts +1 -0
  50. package/dist/utils.js +38 -0
  51. package/package.json +2 -2
  52. package/src/OpenAPICodeSample.tsx +7 -6
  53. package/src/OpenAPIDisclosure.tsx +7 -3
  54. package/src/OpenAPIDisclosureGroup.tsx +49 -48
  55. package/src/OpenAPIRequestBody.tsx +11 -2
  56. package/src/OpenAPIRequestBodyHeaderType.tsx +36 -0
  57. package/src/OpenAPIResponse.tsx +37 -5
  58. package/src/OpenAPIResponseExample.tsx +46 -35
  59. package/src/OpenAPIResponses.tsx +4 -4
  60. package/src/OpenAPISchema.tsx +157 -130
  61. package/src/OpenAPISchemaName.tsx +10 -8
  62. package/src/OpenAPISecurities.tsx +111 -7
  63. package/src/OpenAPISelect.tsx +1 -1
  64. package/src/OpenAPISpec.tsx +21 -1
  65. package/src/OpenAPIWebhookExample.tsx +2 -2
  66. package/src/StaticSection.tsx +1 -1
  67. package/src/code-samples.test.ts +3 -2
  68. package/src/code-samples.ts +19 -12
  69. package/src/generateSchemaExample.test.ts +20 -0
  70. package/src/generateSchemaExample.ts +9 -1
  71. package/src/getDisclosureLabel.ts +25 -0
  72. package/src/schemas/OpenAPISchemaItem.tsx +34 -0
  73. package/src/schemas/OpenAPISchemas.tsx +7 -13
  74. package/src/translations/de.ts +10 -4
  75. package/src/translations/en.ts +9 -3
  76. package/src/translations/es.ts +10 -4
  77. package/src/translations/fr.ts +11 -5
  78. package/src/translations/ja.ts +9 -3
  79. package/src/translations/nl.ts +9 -3
  80. package/src/translations/no.ts +10 -4
  81. package/src/translations/pt-br.ts +10 -4
  82. package/src/translations/zh.ts +10 -4
  83. package/src/utils.ts +37 -0
@@ -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 | undefined;
23
- required?: boolean | undefined;
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(props: {
31
- property: OpenAPISchemaPropertyEntry;
32
- context: OpenAPIClientContext;
33
- circularRefs: CircularRefsIds;
34
- className?: string;
35
- }) {
36
- const { circularRefs: parentCircularRefs, context, className, property } = props;
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
- return (
43
- <div id={id} className={clsx('openapi-schema', className)}>
44
- <OpenAPISchemaPresentation context={context} property={property} />
45
- {(() => {
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
- }
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
- const circularRefs = new Map(parentCircularRefs);
53
- circularRefs.set(schema, id);
54
-
55
- const properties = getSchemaProperties(schema);
56
- if (properties?.length) {
57
- return (
58
- <OpenAPIDisclosure
59
- icon={context.icons.plus}
60
- label={(isExpanded) =>
61
- getDisclosureLabel({ schema, isExpanded, context })
62
- }
63
- >
64
- <OpenAPISchemaProperties
65
- properties={properties}
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
- </OpenAPIDisclosure>
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
- const ancestors = new Set(circularRefs.keys());
74
- const alternatives = getSchemaAlternatives(schema, ancestors);
75
-
76
- if (alternatives) {
77
- return alternatives.map((schema, index) => (
78
- <OpenAPISchemaAlternative
79
- key={index}
80
- schema={schema}
81
- circularRefs={circularRefs}
82
- context={context}
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
- return null;
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
- {description ? (
215
- <Markdown source={description} className="openapi-schema-description" />
216
- ) : null}
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
- Available options:{' '}
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(...schemas);
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
- acc.push(schemaOrRef);
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
- function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
574
- // Otherwise try to infer a nice title
575
- let type = 'any';
576
-
577
- if (schema.enum || schema['x-enumDescriptions'] || schema['x-gitbook-enum']) {
578
- type = `${schema.type} · enum`;
579
- // check array AND schema.items as this is sometimes null despite what the type indicates
580
- } else if (schema.type === 'array' && !!schema.items) {
581
- type = `${getSchemaTitle(schema.items)}[]`;
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
- return type;
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 `${isExpanded ? tString(context.translation, 'hide') : tString(context.translation, 'show')} ${label}`;
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
- <span>
31
- {type ? <span className="openapi-schema-type">{type}</span> : null}
32
- {additionalItems ? (
33
- <span className="openapi-schema-type">{additionalItems}</span>
34
- ) : null}
35
- </span>
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
+ }
@@ -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?: Key) {
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,
@@ -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.schema;
24
+ const schema = value?.schema;
25
25
 
26
26
  if (!schema) {
27
27
  return {
@@ -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 && `${props.className}-header`
14
+ props.className ? `${props.className}-header` : undefined
15
15
  )}
16
16
  />
17
17
  );
@@ -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","truethy":True,"falsey":False})\n)\n\ndata = response.json()'
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