@gitbook/react-openapi 0.7.1 → 1.0.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 +41 -0
- package/dist/InteractiveSection.d.ts +4 -6
- package/dist/InteractiveSection.jsx +96 -0
- package/dist/Markdown.d.ts +1 -2
- package/dist/Markdown.jsx +5 -0
- package/dist/OpenAPICodeSample.d.ts +2 -4
- package/dist/OpenAPICodeSample.jsx +143 -0
- package/dist/OpenAPIDisclosure.d.ts +12 -0
- package/dist/OpenAPIDisclosure.jsx +32 -0
- package/dist/OpenAPIDisclosureGroup.d.ts +19 -0
- package/dist/OpenAPIDisclosureGroup.jsx +81 -0
- package/dist/OpenAPIOperation.d.ts +2 -4
- package/dist/OpenAPIOperation.jsx +51 -0
- package/dist/OpenAPIOperationContext.d.ts +16 -0
- package/dist/OpenAPIOperationContext.jsx +26 -0
- package/dist/OpenAPIPath.d.ts +8 -0
- package/dist/OpenAPIPath.jsx +54 -0
- package/dist/OpenAPIRequestBody.d.ts +3 -4
- package/dist/OpenAPIRequestBody.jsx +19 -0
- package/dist/OpenAPIResponse.d.ts +4 -4
- package/dist/OpenAPIResponse.jsx +49 -0
- package/dist/OpenAPIResponseExample.d.ts +2 -4
- package/dist/OpenAPIResponseExample.jsx +108 -0
- package/dist/OpenAPIResponses.d.ts +3 -4
- package/dist/OpenAPIResponses.jsx +36 -0
- package/dist/OpenAPISchema.d.ts +11 -8
- package/dist/OpenAPISchema.jsx +295 -0
- package/dist/OpenAPISchemaName.d.ts +12 -0
- package/dist/OpenAPISchemaName.jsx +15 -0
- package/dist/OpenAPISecurities.d.ts +2 -4
- package/dist/OpenAPISecurities.jsx +55 -0
- package/dist/OpenAPIServerURL.d.ts +2 -3
- package/dist/OpenAPIServerURL.jsx +67 -0
- package/dist/OpenAPIServerURLVariable.d.ts +2 -3
- package/dist/OpenAPIServerURLVariable.jsx +8 -0
- package/dist/OpenAPISpec.d.ts +3 -4
- package/dist/OpenAPISpec.jsx +91 -0
- package/dist/OpenAPITabs.d.ts +25 -0
- package/dist/OpenAPITabs.jsx +67 -0
- package/dist/ScalarApiButton.d.ts +3 -3
- package/dist/ScalarApiButton.jsx +51 -0
- package/dist/code-samples.d.ts +4 -0
- package/dist/code-samples.js +103 -38
- package/dist/fetchOpenAPIOperation.d.ts +9 -54
- package/dist/fetchOpenAPIOperation.js +178 -107
- package/dist/generateSchemaExample.d.ts +2 -2
- package/dist/generateSchemaExample.js +28 -100
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/resolveOpenAPIOperation.d.ts +11 -0
- package/dist/resolveOpenAPIOperation.js +194 -0
- package/dist/stringifyOpenAPI.d.ts +4 -0
- package/dist/stringifyOpenAPI.js +6 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +11 -12
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +15 -2
- package/package.json +11 -10
- package/src/InteractiveSection.tsx +119 -78
- package/src/Markdown.tsx +2 -3
- package/src/OpenAPICodeSample.tsx +35 -21
- package/src/OpenAPIDisclosure.tsx +50 -0
- package/src/OpenAPIDisclosureGroup.tsx +136 -0
- package/src/OpenAPIOperation.tsx +36 -42
- package/src/OpenAPIOperationContext.tsx +45 -0
- package/src/OpenAPIPath.tsx +65 -0
- package/src/OpenAPIRequestBody.tsx +3 -14
- package/src/OpenAPIResponse.tsx +39 -43
- package/src/OpenAPIResponseExample.tsx +89 -31
- package/src/OpenAPIResponses.tsx +51 -15
- package/src/OpenAPISchema.test.ts +1 -1
- package/src/OpenAPISchema.tsx +124 -92
- package/src/OpenAPISchemaName.tsx +27 -0
- package/src/OpenAPISecurities.tsx +45 -24
- package/src/OpenAPIServerURL.tsx +17 -10
- package/src/OpenAPIServerURLVariable.tsx +2 -4
- package/src/OpenAPISpec.tsx +56 -53
- package/src/OpenAPITabs.tsx +113 -0
- package/src/ScalarApiButton.tsx +84 -7
- package/src/code-samples.test.ts +51 -0
- package/src/code-samples.ts +95 -31
- package/src/generateSchemaExample.ts +25 -151
- package/src/index.ts +3 -2
- package/src/resolveOpenAPIOperation.test.ts +177 -0
- package/src/resolveOpenAPIOperation.ts +163 -0
- package/src/stringifyOpenAPI.ts +6 -0
- package/src/types.ts +17 -10
- package/src/utils.ts +17 -2
- package/dist/InteractiveSection.js +0 -47
- package/dist/Markdown.js +0 -6
- package/dist/OpenAPICodeSample.js +0 -110
- package/dist/OpenAPIOperation.js +0 -38
- package/dist/OpenAPIRequestBody.js +0 -18
- package/dist/OpenAPIResponse.js +0 -32
- package/dist/OpenAPIResponseExample.js +0 -54
- package/dist/OpenAPIResponses.js +0 -18
- package/dist/OpenAPISchema.js +0 -235
- package/dist/OpenAPISchema.test.d.ts +0 -1
- package/dist/OpenAPISchema.test.js +0 -91
- package/dist/OpenAPISecurities.js +0 -42
- package/dist/OpenAPIServerURL.js +0 -51
- package/dist/OpenAPIServerURLVariable.js +0 -10
- package/dist/OpenAPISpec.js +0 -70
- package/dist/ScalarApiButton.js +0 -14
- package/dist/fetchOpenAPIOperation.test.d.ts +0 -1
- package/dist/fetchOpenAPIOperation.test.js +0 -152
- package/dist/resolveOpenAPIPath.d.ts +0 -7
- package/dist/resolveOpenAPIPath.js +0 -112
- package/dist/resolveOpenAPIPath.test.d.ts +0 -1
- package/dist/resolveOpenAPIPath.test.js +0 -39
- package/src/fetchOpenAPIOperation.test.ts +0 -185
- package/src/fetchOpenAPIOperation.ts +0 -230
- package/src/resolveOpenAPIPath.test.ts +0 -60
- package/src/resolveOpenAPIPath.ts +0 -145
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
groups: TDisclosureGroup[];
|
|
3
|
+
icon?: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type TDisclosureGroup = {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string | React.ReactNode;
|
|
9
|
+
tabs?: {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string | React.ReactNode;
|
|
12
|
+
body?: React.ReactNode;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
import { mergeProps, useButton, useDisclosure, useFocusRing, useId } from 'react-aria';
|
|
17
|
+
import {
|
|
18
|
+
DisclosureGroupProps,
|
|
19
|
+
DisclosureGroupState,
|
|
20
|
+
useDisclosureGroupState,
|
|
21
|
+
useDisclosureState,
|
|
22
|
+
} from 'react-stately';
|
|
23
|
+
import { createContext, useContext, useRef, useState } from 'react';
|
|
24
|
+
|
|
25
|
+
const DisclosureGroupStateContext = createContext<DisclosureGroupState | null>(null);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Display an interactive OpenAPI disclosure group.
|
|
29
|
+
*/
|
|
30
|
+
export function OpenAPIDisclosureGroup(props: DisclosureGroupProps & Props) {
|
|
31
|
+
const { icon, groups } = props;
|
|
32
|
+
|
|
33
|
+
const state = useDisclosureGroupState(props);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<DisclosureGroupStateContext.Provider value={state}>
|
|
37
|
+
{groups.map((group) => (
|
|
38
|
+
<DisclosureItem icon={icon} key={group.id} group={group} />
|
|
39
|
+
))}
|
|
40
|
+
</DisclosureGroupStateContext.Provider>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function DisclosureItem(props: { group: TDisclosureGroup; icon?: React.ReactNode }) {
|
|
45
|
+
const { icon, group } = props;
|
|
46
|
+
|
|
47
|
+
const defaultId = useId();
|
|
48
|
+
const id = group.id || defaultId;
|
|
49
|
+
const groupState = useContext(DisclosureGroupStateContext);
|
|
50
|
+
const isExpanded = groupState?.expandedKeys.has(id) || false;
|
|
51
|
+
const state = useDisclosureState({
|
|
52
|
+
isExpanded,
|
|
53
|
+
onExpandedChange() {
|
|
54
|
+
if (groupState) {
|
|
55
|
+
groupState.toggleKey(id);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
61
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
62
|
+
const isDisabled = groupState?.isDisabled || !group.tabs?.length || false;
|
|
63
|
+
const { buttonProps: triggerProps, panelProps } = useDisclosure(
|
|
64
|
+
{
|
|
65
|
+
...props,
|
|
66
|
+
isExpanded,
|
|
67
|
+
isDisabled,
|
|
68
|
+
},
|
|
69
|
+
state,
|
|
70
|
+
panelRef,
|
|
71
|
+
);
|
|
72
|
+
const { buttonProps } = useButton(triggerProps, triggerRef);
|
|
73
|
+
const { isFocusVisible, focusProps } = useFocusRing();
|
|
74
|
+
|
|
75
|
+
const defaultTab = group.tabs?.[0]?.id || '';
|
|
76
|
+
const [selectedTabKey, setSelectedTabKey] = useState(defaultTab);
|
|
77
|
+
const selectedTab = group.tabs?.find((tab) => tab.id === selectedTabKey);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className="openapi-disclosure-group" aria-expanded={state.isExpanded}>
|
|
81
|
+
<div className="openapi-disclosure-group-header">
|
|
82
|
+
<button
|
|
83
|
+
slot="trigger"
|
|
84
|
+
ref={triggerRef}
|
|
85
|
+
{...mergeProps(buttonProps, focusProps)}
|
|
86
|
+
disabled={isDisabled}
|
|
87
|
+
style={{
|
|
88
|
+
outline: isFocusVisible
|
|
89
|
+
? '2px solid rgb(var(--primary-color-500)/0.4)'
|
|
90
|
+
: 'none',
|
|
91
|
+
}}
|
|
92
|
+
className="openapi-disclosure-group-trigger"
|
|
93
|
+
>
|
|
94
|
+
<div className="openapi-disclosure-group-icon">
|
|
95
|
+
{icon || (
|
|
96
|
+
<svg viewBox="0 0 24 24" className="openapi-disclosure-group-icon">
|
|
97
|
+
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
|
98
|
+
</svg>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{group.label}
|
|
103
|
+
</button>
|
|
104
|
+
{group.tabs ? (
|
|
105
|
+
<div className="openapi-disclosure-group-mediatype">
|
|
106
|
+
{group.tabs?.length > 1 ? (
|
|
107
|
+
<select
|
|
108
|
+
className="openapi-section-select openapi-select openapi-disclosure-group-tabs-select"
|
|
109
|
+
onClick={(event) => event.stopPropagation()}
|
|
110
|
+
value={selectedTab?.id}
|
|
111
|
+
onChange={(event) => {
|
|
112
|
+
setSelectedTabKey(event.target.value);
|
|
113
|
+
state.expand();
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
{group.tabs.map((tab) => (
|
|
117
|
+
<option key={tab.id} value={tab.id}>
|
|
118
|
+
{tab.label}
|
|
119
|
+
</option>
|
|
120
|
+
))}
|
|
121
|
+
</select>
|
|
122
|
+
) : !!group.tabs[0] ? (
|
|
123
|
+
<span>{group.tabs[0].label}</span>
|
|
124
|
+
) : null}
|
|
125
|
+
</div>
|
|
126
|
+
) : null}
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{state.isExpanded && selectedTab && (
|
|
130
|
+
<div className="openapi-disclosure-group-panel" ref={panelRef} {...panelProps}>
|
|
131
|
+
{selectedTab.body}
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
package/src/OpenAPIOperation.tsx
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import classNames from 'classnames';
|
|
3
|
-
import { ApiClientModalProvider } from '@scalar/api-client-react';
|
|
1
|
+
import clsx from 'clsx';
|
|
4
2
|
|
|
5
|
-
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
|
|
6
3
|
import { Markdown } from './Markdown';
|
|
7
4
|
import { OpenAPICodeSample } from './OpenAPICodeSample';
|
|
8
5
|
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
|
9
|
-
import { OpenAPIServerURL } from './OpenAPIServerURL';
|
|
10
6
|
import { OpenAPISpec } from './OpenAPISpec';
|
|
11
|
-
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
|
|
7
|
+
import type { OpenAPIClientContext, OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
8
|
+
import { OpenAPIPath } from './OpenAPIPath';
|
|
9
|
+
import { resolveDescription } from './utils';
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* Display an interactive OpenAPI operation.
|
|
@@ -19,7 +17,7 @@ export function OpenAPIOperation(props: {
|
|
|
19
17
|
context: OpenAPIContextProps;
|
|
20
18
|
}) {
|
|
21
19
|
const { className, data, context } = props;
|
|
22
|
-
const { operation
|
|
20
|
+
const { operation } = data;
|
|
23
21
|
|
|
24
22
|
const clientContext: OpenAPIClientContext = {
|
|
25
23
|
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
|
@@ -27,46 +25,42 @@ export function OpenAPIOperation(props: {
|
|
|
27
25
|
blockKey: context.blockKey,
|
|
28
26
|
};
|
|
29
27
|
|
|
28
|
+
const description = resolveDescription(operation)?.trim();
|
|
29
|
+
|
|
30
30
|
return (
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<div className="openapi-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{operation
|
|
41
|
-
<
|
|
31
|
+
<div className={clsx('openapi-operation', className)}>
|
|
32
|
+
<div className="openapi-summary" id={context.id}>
|
|
33
|
+
<h2 className="openapi-summary-title" data-deprecated={operation.deprecated}>
|
|
34
|
+
{operation.summary}
|
|
35
|
+
</h2>
|
|
36
|
+
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
|
37
|
+
</div>
|
|
38
|
+
<div className="openapi-columns">
|
|
39
|
+
<div className="openapi-column-spec">
|
|
40
|
+
{operation['x-deprecated-sunset'] ? (
|
|
41
|
+
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
|
|
42
|
+
This operation is deprecated and will be sunset on{' '}
|
|
43
|
+
<span className="openapi-deprecated-sunset-date">
|
|
44
|
+
{operation['x-deprecated-sunset']}
|
|
45
|
+
</span>
|
|
46
|
+
{`.`}
|
|
47
|
+
</div>
|
|
42
48
|
) : null}
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
className={
|
|
46
|
-
'openapi-method',
|
|
47
|
-
`openapi-method-${method.toLowerCase()}`,
|
|
48
|
-
)}
|
|
49
|
-
>
|
|
50
|
-
{method.toUpperCase()}
|
|
51
|
-
</span>
|
|
52
|
-
<span className="openapi-url">
|
|
53
|
-
<OpenAPIServerURL servers={servers} />
|
|
54
|
-
{path}
|
|
55
|
-
</span>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
<div className={classNames('openapi-columns')}>
|
|
59
|
-
<div className={classNames('openapi-column-spec')}>
|
|
60
|
-
<OpenAPISpec rawData={toJSON(data)} context={clientContext} />
|
|
61
|
-
</div>
|
|
62
|
-
<div className={classNames('openapi-column-preview')}>
|
|
63
|
-
<div className={classNames('openapi-column-preview-body')}>
|
|
64
|
-
<OpenAPICodeSample {...props} />
|
|
65
|
-
<OpenAPIResponseExample {...props} />
|
|
49
|
+
{description ? (
|
|
50
|
+
<div className="openapi-intro">
|
|
51
|
+
<Markdown className="openapi-description" source={description} />
|
|
66
52
|
</div>
|
|
53
|
+
) : null}
|
|
54
|
+
<OpenAPIPath data={data} context={context} />
|
|
55
|
+
<OpenAPISpec data={data} context={clientContext} />
|
|
56
|
+
</div>
|
|
57
|
+
<div className="openapi-column-preview">
|
|
58
|
+
<div className="openapi-column-preview-body">
|
|
59
|
+
<OpenAPICodeSample {...props} />
|
|
60
|
+
<OpenAPIResponseExample {...props} />
|
|
67
61
|
</div>
|
|
68
62
|
</div>
|
|
69
63
|
</div>
|
|
70
|
-
</
|
|
64
|
+
</div>
|
|
71
65
|
);
|
|
72
66
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
4
|
+
import { useEventCallback } from 'usehooks-ts';
|
|
5
|
+
|
|
6
|
+
interface OpenAPIOperationPointer {
|
|
7
|
+
path: string;
|
|
8
|
+
method: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface OpenAPIOperationContextValue {
|
|
12
|
+
onOpenClient: (pointer: OpenAPIOperationPointer) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const OpenAPIOperationContext = createContext<OpenAPIOperationContextValue>({
|
|
16
|
+
onOpenClient: () => {},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Provider for the OpenAPIOperationContext.
|
|
21
|
+
*/
|
|
22
|
+
export function OpenAPIOperationContextProvider(
|
|
23
|
+
props: React.PropsWithChildren<Partial<OpenAPIOperationContextValue>>,
|
|
24
|
+
) {
|
|
25
|
+
const { children } = props;
|
|
26
|
+
|
|
27
|
+
const onOpenClient = useEventCallback((pointer: OpenAPIOperationPointer) => {
|
|
28
|
+
props.onOpenClient?.(pointer);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const value = useMemo(() => ({ onOpenClient }), [onOpenClient]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<OpenAPIOperationContext.Provider value={value}>
|
|
35
|
+
{children}
|
|
36
|
+
</OpenAPIOperationContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hook to access the OpenAPIOperationContext.
|
|
42
|
+
*/
|
|
43
|
+
export function useOpenAPIOperationContext() {
|
|
44
|
+
return useContext(OpenAPIOperationContext);
|
|
45
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ScalarApiButton } from './ScalarApiButton';
|
|
2
|
+
import type { OpenAPIOperationData, OpenAPIContextProps } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Display the path of an operation.
|
|
6
|
+
*/
|
|
7
|
+
export function OpenAPIPath(props: {
|
|
8
|
+
data: OpenAPIOperationData;
|
|
9
|
+
context: OpenAPIContextProps;
|
|
10
|
+
}): JSX.Element {
|
|
11
|
+
const { data, context } = props;
|
|
12
|
+
const { method, path } = data;
|
|
13
|
+
const { specUrl } = context;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="openapi-path">
|
|
17
|
+
<div className={`openapi-method openapi-method-${method}`}>{method}</div>
|
|
18
|
+
<div className="openapi-path-title" data-deprecated={data.operation.deprecated}>
|
|
19
|
+
<p>{formatPath(path)}</p>
|
|
20
|
+
</div>
|
|
21
|
+
{data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
|
|
22
|
+
<ScalarApiButton method={method} path={path} specUrl={specUrl} />
|
|
23
|
+
)}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Format the path to highlight placeholders
|
|
29
|
+
function formatPath(path: string) {
|
|
30
|
+
// Matches placeholders like {id}, {userId}, etc.
|
|
31
|
+
const regex = /\{(\w+)\}/g;
|
|
32
|
+
|
|
33
|
+
const parts: (string | JSX.Element)[] = [];
|
|
34
|
+
let lastIndex = 0;
|
|
35
|
+
|
|
36
|
+
// Replace placeholders with <em> tags
|
|
37
|
+
path.replace(regex, (match, key, offset) => {
|
|
38
|
+
parts.push(path.slice(lastIndex, offset));
|
|
39
|
+
parts.push(<em key={key}>{`{${key}}`}</em>);
|
|
40
|
+
lastIndex = offset + match.length;
|
|
41
|
+
return match;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Push remaining text after the last placeholder
|
|
45
|
+
parts.push(path.slice(lastIndex));
|
|
46
|
+
|
|
47
|
+
// Join parts with separators wrapped in <span>
|
|
48
|
+
const formattedPath = parts.reduce(
|
|
49
|
+
(acc, part, index) => {
|
|
50
|
+
if (typeof part === 'string' && index > 0 && part === '/') {
|
|
51
|
+
return [
|
|
52
|
+
...acc,
|
|
53
|
+
<span className="openapi-path-separator" key={`sep-${index}`}>
|
|
54
|
+
/
|
|
55
|
+
</span>,
|
|
56
|
+
part,
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
return [...acc, part];
|
|
60
|
+
},
|
|
61
|
+
[] as (string | JSX.Element)[],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return <span>{formattedPath}</span>;
|
|
65
|
+
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { OpenAPIV3 } from 'openapi-types';
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
4
2
|
import { OpenAPIRootSchema } from './OpenAPISchema';
|
|
5
3
|
import { noReference } from './utils';
|
|
6
|
-
import { OpenAPIClientContext } from './types';
|
|
4
|
+
import type { OpenAPIClientContext } from './types';
|
|
7
5
|
import { InteractiveSection } from './InteractiveSection';
|
|
8
|
-
import { Markdown } from './Markdown';
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* Display an interactive request body.
|
|
@@ -34,14 +31,6 @@ export function OpenAPIRequestBody(props: {
|
|
|
34
31
|
};
|
|
35
32
|
},
|
|
36
33
|
)}
|
|
37
|
-
|
|
38
|
-
>
|
|
39
|
-
{requestBody.description ? (
|
|
40
|
-
<Markdown
|
|
41
|
-
source={requestBody.description}
|
|
42
|
-
className="openapi-requestbody-description"
|
|
43
|
-
/>
|
|
44
|
-
) : null}
|
|
45
|
-
</InteractiveSection>
|
|
34
|
+
/>
|
|
46
35
|
);
|
|
47
36
|
}
|
package/src/OpenAPIResponse.tsx
CHANGED
|
@@ -1,44 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { OpenAPIClientContext } from './types';
|
|
7
|
-
import { InteractiveSection } from './InteractiveSection';
|
|
8
|
-
import { Markdown } from './Markdown';
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
|
+
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
|
3
|
+
import { checkIsReference, noReference, resolveDescription } from './utils';
|
|
4
|
+
import type { OpenAPIClientContext } from './types';
|
|
5
|
+
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* Display an interactive response body.
|
|
12
9
|
*/
|
|
13
10
|
export function OpenAPIResponse(props: {
|
|
14
11
|
response: OpenAPIV3.ResponseObject;
|
|
12
|
+
mediaType: OpenAPIV3.MediaTypeObject;
|
|
15
13
|
context: OpenAPIClientContext;
|
|
16
14
|
}) {
|
|
17
|
-
const { response, context } = props;
|
|
18
|
-
const content = Object.entries(response.content ?? {});
|
|
15
|
+
const { response, context, mediaType } = props;
|
|
19
16
|
const headers = Object.entries(response.headers ?? {}).map(
|
|
20
17
|
([name, header]) => [name, noReference(header) ?? {}] as const,
|
|
21
18
|
);
|
|
19
|
+
const content = Object.entries(mediaType.schema ?? {});
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
const description = resolveDescription(response);
|
|
22
|
+
|
|
23
|
+
if (content.length === 0 && !description && headers.length === 0) {
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
|
-
|
|
29
|
-
{response.description ? (
|
|
30
|
-
<Markdown source={response.description} className="openapi-response-description" />
|
|
31
|
-
) : null}
|
|
32
|
-
|
|
28
|
+
<div className="openapi-response-body">
|
|
33
29
|
{headers.length > 0 ? (
|
|
34
|
-
<
|
|
35
|
-
toggeable
|
|
36
|
-
defaultOpened={!!context.defaultInteractiveOpened}
|
|
37
|
-
toggleCloseIcon={context.icons.chevronDown}
|
|
38
|
-
toggleOpenIcon={context.icons.chevronRight}
|
|
39
|
-
header="Headers"
|
|
40
|
-
className={classNames('openapi-responseheaders')}
|
|
41
|
-
>
|
|
30
|
+
<OpenAPIDisclosure context={context} label={'Headers'}>
|
|
42
31
|
<OpenAPISchemaProperties
|
|
43
32
|
properties={headers.map(([name, header]) => ({
|
|
44
33
|
propertyName: name,
|
|
@@ -47,26 +36,33 @@ export function OpenAPIResponse(props: {
|
|
|
47
36
|
}))}
|
|
48
37
|
context={context}
|
|
49
38
|
/>
|
|
50
|
-
</
|
|
39
|
+
</OpenAPIDisclosure>
|
|
51
40
|
) : null}
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<OpenAPIRootSchema
|
|
62
|
-
schema={noReference(mediaType.schema) ?? {}}
|
|
63
|
-
context={context}
|
|
64
|
-
/>
|
|
65
|
-
),
|
|
66
|
-
};
|
|
67
|
-
})}
|
|
41
|
+
<div className="openapi-responsebody">
|
|
42
|
+
<OpenAPISchemaProperties
|
|
43
|
+
id={`response-${context.blockKey}`}
|
|
44
|
+
properties={[
|
|
45
|
+
{
|
|
46
|
+
schema: handleUnresolvedReference(mediaType.schema) ?? {},
|
|
47
|
+
},
|
|
48
|
+
]}
|
|
49
|
+
context={context}
|
|
68
50
|
/>
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
71
53
|
);
|
|
72
54
|
}
|
|
55
|
+
|
|
56
|
+
function handleUnresolvedReference(
|
|
57
|
+
input: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
|
|
58
|
+
): OpenAPIV3.SchemaObject {
|
|
59
|
+
const isReference = checkIsReference(input);
|
|
60
|
+
|
|
61
|
+
if (isReference || input === undefined) {
|
|
62
|
+
// If we find a reference that wasn't resolved or needed to be resolved externally, do not try to render it.
|
|
63
|
+
// Instead we render `any`
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return input;
|
|
68
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { InteractiveSection } from './InteractiveSection';
|
|
3
|
-
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
4
2
|
import { generateSchemaExample } from './generateSchemaExample';
|
|
5
|
-
import { OpenAPIContextProps } from './types';
|
|
6
|
-
import {
|
|
3
|
+
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
4
|
+
import { checkIsReference, noReference, resolveDescription } from './utils';
|
|
5
|
+
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
6
|
+
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
|
7
|
+
import { InteractiveSection } from './InteractiveSection';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Display an example of the response content.
|
|
@@ -38,50 +39,107 @@ export function OpenAPIResponseExample(props: {
|
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
const examples = responses
|
|
41
|
-
.map((
|
|
42
|
-
const responseObject = noReference(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
.map(([key, value]) => {
|
|
43
|
+
const responseObject = noReference(value);
|
|
44
|
+
const mediaTypeObject = (() => {
|
|
45
|
+
if (!responseObject.content) {
|
|
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
|
+
})();
|
|
50
54
|
|
|
51
|
-
if (!
|
|
52
|
-
return
|
|
55
|
+
if (!mediaTypeObject) {
|
|
56
|
+
return {
|
|
57
|
+
key: key,
|
|
58
|
+
label: key,
|
|
59
|
+
description: resolveDescription(responseObject),
|
|
60
|
+
body: <OpenAPIEmptyResponseExample />,
|
|
61
|
+
};
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
const example =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
const example = handleUnresolvedReference(
|
|
65
|
+
(() => {
|
|
66
|
+
const { examples, example } = mediaTypeObject;
|
|
67
|
+
if (examples) {
|
|
68
|
+
const key = Object.keys(examples)[0];
|
|
69
|
+
if (key) {
|
|
70
|
+
// @TODO handle multiple examples
|
|
71
|
+
const firstExample = noReference(examples[key]);
|
|
72
|
+
if (firstExample) {
|
|
73
|
+
return firstExample;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (example) {
|
|
79
|
+
return { value: example };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const schema = noReference(mediaTypeObject.schema);
|
|
83
|
+
if (!schema) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { value: generateSchemaExample(schema) };
|
|
88
|
+
})(),
|
|
89
|
+
);
|
|
59
90
|
|
|
60
91
|
return {
|
|
61
|
-
key:
|
|
62
|
-
label:
|
|
63
|
-
|
|
92
|
+
key: key,
|
|
93
|
+
label: key,
|
|
94
|
+
description: resolveDescription(responseObject),
|
|
95
|
+
body: example?.value ? (
|
|
64
96
|
<context.CodeBlock
|
|
65
97
|
code={
|
|
66
|
-
typeof example === 'string'
|
|
98
|
+
typeof example.value === 'string'
|
|
99
|
+
? example.value
|
|
100
|
+
: stringifyOpenAPI(example.value, null, 2)
|
|
67
101
|
}
|
|
68
102
|
syntax="json"
|
|
69
103
|
/>
|
|
104
|
+
) : (
|
|
105
|
+
<OpenAPIEmptyResponseExample />
|
|
70
106
|
),
|
|
71
107
|
};
|
|
72
108
|
})
|
|
73
|
-
.filter((val): val is { key: string; label: string; body: any } =>
|
|
109
|
+
.filter((val): val is { key: string; label: string; body: any; description: string } =>
|
|
110
|
+
Boolean(val),
|
|
111
|
+
);
|
|
74
112
|
|
|
75
113
|
if (examples.length === 0) {
|
|
76
114
|
return null;
|
|
77
115
|
}
|
|
78
116
|
|
|
79
117
|
return (
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/>
|
|
118
|
+
<OpenAPITabs items={examples}>
|
|
119
|
+
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
|
120
|
+
<OpenAPITabsPanels />
|
|
121
|
+
</InteractiveSection>
|
|
122
|
+
</OpenAPITabs>
|
|
86
123
|
);
|
|
87
124
|
}
|
|
125
|
+
|
|
126
|
+
function OpenAPIEmptyResponseExample() {
|
|
127
|
+
return (
|
|
128
|
+
<pre className="openapi-response-example-empty">
|
|
129
|
+
<p>No body</p>
|
|
130
|
+
</pre>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function handleUnresolvedReference(
|
|
135
|
+
input: OpenAPIV3.ExampleObject | null,
|
|
136
|
+
): OpenAPIV3.ExampleObject | null {
|
|
137
|
+
const isReference = checkIsReference(input?.value);
|
|
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;
|
|
145
|
+
}
|