Skip to content

Commit bdd6303

Browse files
authored
Rearrange item types in OpenAPI blocks (#2862)
1 parent 66d0fc0 commit bdd6303

File tree

4 files changed

+104
-67
lines changed

4 files changed

+104
-67
lines changed

packages/gitbook/src/components/DocumentView/OpenAPI/style.css

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
}
2222

2323
.openapi-deprecated-sunset-date {
24-
@apply font-semibold font-mono;
24+
@apply font-semibold font-mono truncate;
2525
}
2626

2727
.openapi-description.openapi-markdown {
@@ -181,7 +181,7 @@
181181
.openapi-schema-name {
182182
/* To make double click on the property name select only the name,
183183
we disable selection on the parent and re-enable it on the children. */
184-
@apply select-none flex gap-2.5 items-baseline text-sm;
184+
@apply select-none flex gap-x-2.5 items-baseline text-sm flex-wrap;
185185
}
186186

187187
.openapi-schema-name .openapi-deprecated {
@@ -571,11 +571,15 @@
571571

572572
/* Disclosure */
573573
.openapi-disclosure-trigger {
574-
@apply transition-all duration-300 hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
574+
@apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
575+
}
576+
577+
.openapi-disclosure-trigger span {
578+
@apply truncate;
575579
}
576580

577581
.openapi-disclosure svg {
578-
@apply size-3 transition-transform duration-300;
582+
@apply size-3 shrink-0 transition-transform duration-300;
579583
}
580584

581585
.openapi-disclosure-trigger[aria-expanded='true'] svg {

packages/react-openapi/src/OpenAPISchema.tsx

Lines changed: 54 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,6 @@ export function OpenAPISchemaProperty(
4747
? null
4848
: getSchemaAlternatives(schema, new Set(circularRefs.keys()));
4949

50-
if ((properties && properties.length > 0) || 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-
6750
if (alternatives?.[0]?.length) {
6851
return (
6952
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
@@ -80,6 +63,23 @@ export function OpenAPISchemaProperty(
8063
);
8164
}
8265

66+
if ((properties && properties.length > 0) || schema.type === 'object') {
67+
return (
68+
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
69+
<OpenAPISchemaPresentation {...props} />
70+
{properties && properties.length > 0 ? (
71+
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
72+
<OpenAPISchemaProperties
73+
properties={properties}
74+
circularRefs={circularRefs}
75+
context={context}
76+
/>
77+
</OpenAPIDisclosure>
78+
) : null}
79+
</InteractiveSection>
80+
);
81+
}
82+
8383
return (
8484
<InteractiveSection id={id} className={clsx('openapi-schema', className)}>
8585
<OpenAPISchemaPresentation {...props} />
@@ -166,16 +166,24 @@ function OpenAPISchemaAlternative(props: {
166166
const { schema, circularRefs, context } = props;
167167
const id = useId();
168168
const subProperties = getSchemaProperties(schema);
169+
const description = resolveDescription(schema);
169170

170171
return (
171-
<OpenAPIDisclosure context={context}>
172-
<OpenAPISchemaProperties
173-
id={id}
174-
properties={subProperties ?? [{ schema }]}
175-
circularRefs={subProperties ? new Map(circularRefs).set(schema, id) : circularRefs}
176-
context={context}
177-
/>
178-
</OpenAPIDisclosure>
172+
<>
173+
{description ? (
174+
<Markdown source={description} className="openapi-schema-description" />
175+
) : null}
176+
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}>
177+
<OpenAPISchemaProperties
178+
id={id}
179+
properties={subProperties ?? [{ schema }]}
180+
circularRefs={
181+
subProperties ? new Map(circularRefs).set(schema, id) : circularRefs
182+
}
183+
context={context}
184+
/>
185+
</OpenAPIDisclosure>
186+
</>
179187
);
180188
}
181189

@@ -219,7 +227,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
219227

220228
const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => {
221229
return (
222-
typeof schema.example === 'string' ||
230+
(typeof schema.example === 'string' && !!schema.example) ||
223231
typeof schema.example === 'number' ||
224232
typeof schema.example === 'boolean' ||
225233
(Array.isArray(schema.example) && schema.example.length > 0) ||
@@ -234,10 +242,10 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
234242
return (
235243
<div className="openapi-schema-presentation">
236244
<OpenAPISchemaName
245+
schema={schema}
237246
type={getSchemaTitle(schema)}
238247
propertyName={propertyName}
239248
required={required}
240-
deprecated={schema.deprecated}
241249
/>
242250
{schema['x-deprecated-sunset'] ? (
243251
<div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
@@ -276,17 +284,6 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
276284
* Get the sub-properties of a schema.
277285
*/
278286
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-
290287
// check array AND schema.items as this is sometimes null despite what the type indicates
291288
if (schema.type === 'array' && !!schema.items) {
292289
const items = schema.items;
@@ -295,6 +292,11 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
295292
return itemProperties;
296293
}
297294

295+
// If the items are a primitive type, we don't need to display them
296+
if (['string', 'number', 'boolean', 'integer'].includes(items.type) && !items.enum) {
297+
return null;
298+
}
299+
298300
return [
299301
{
300302
propertyName: 'items',
@@ -351,8 +353,7 @@ export function getSchemaAlternatives(
351353
}
352354

353355
if (schema.allOf) {
354-
// allOf is managed in `getSchemaProperties`
355-
return null;
356+
return [flattenAlternatives('allOf', schema.allOf, downAncestors), schema.discriminator];
356357
}
357358

358359
return null;
@@ -378,11 +379,6 @@ export function getSchemaTitle(
378379
/** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */
379380
discriminator?: OpenAPIV3.DiscriminatorObject,
380381
): string {
381-
if (schema.title) {
382-
// If the schema has a title, use it
383-
return schema.title;
384-
}
385-
386382
// Try using the discriminator
387383
if (discriminator?.propertyName && schema.properties) {
388384
const discriminatorProperty = schema.properties[discriminator.propertyName];
@@ -419,21 +415,22 @@ export function getSchemaTitle(
419415
type = 'not';
420416
}
421417

422-
if (schema.minimum || schema.minLength) {
423-
type += ` · min: ${schema.minimum || schema.minLength}`;
424-
}
418+
return type;
419+
}
425420

426-
if (schema.maximum || schema.maxLength) {
427-
type += ` · max: ${schema.maximum || schema.maxLength}`;
428-
}
421+
function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined {
422+
if (schema.type === 'array' && !!schema.items) {
423+
if (schema.items.oneOf) {
424+
return 'available items';
425+
}
429426

430-
if (schema.default) {
431-
type += ` · default: ${schema.default}`;
432-
}
427+
// Fallback to "child attributes" for enums and objects
428+
if (schema.items.enum || schema.items.type === 'object') {
429+
return;
430+
}
433431

434-
if (schema.nullable) {
435-
type = `${type} | nullable`;
432+
return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
436433
}
437434

438-
return type;
435+
return schema.title;
439436
}
Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,59 @@
1+
import { OpenAPIV3 } from '@gitbook/openapi-parser';
2+
13
interface OpenAPISchemaNameProps {
4+
schema?: OpenAPIV3.SchemaObject;
25
propertyName?: string | JSX.Element;
36
required?: boolean;
47
type?: string;
5-
deprecated?: boolean;
68
}
79

810
/**
911
* Display the schema name row.
1012
* It includes the property name, type, required and deprecated status.
1113
*/
1214
export function OpenAPISchemaName(props: OpenAPISchemaNameProps): JSX.Element {
13-
const { type, propertyName, required, deprecated } = props;
15+
const { schema, type, propertyName, required } = props;
16+
17+
const additionalItems = schema && getAdditionalItems(schema);
1418

1519
return (
1620
<div className="openapi-schema-name">
1721
{propertyName ? (
18-
<span data-deprecated={deprecated} className="openapi-schema-propertyname">
22+
<span data-deprecated={schema?.deprecated} className="openapi-schema-propertyname">
1923
{propertyName}
2024
</span>
2125
) : null}
22-
{type ? <span className="openapi-schema-type">{type}</span> : null}
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>
2332
{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}
2534
</div>
2635
);
2736
}
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+
}

packages/react-openapi/src/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ export function createStateKey(key: string, scope?: string) {
1313
/**
1414
* Resolve the description of an object.
1515
*/
16-
export function resolveDescription(object: AnyObject) {
16+
export function resolveDescription(object: OpenAPIV3.SchemaObject | AnyObject) {
17+
if ('items' in object && object.items) {
18+
return resolveDescription(object.items);
19+
}
20+
1721
return 'x-gitbook-description-html' in object &&
1822
typeof object['x-gitbook-description-html'] === 'string'
1923
? object['x-gitbook-description-html'].trim()

0 commit comments

Comments
 (0)