@gitbook/react-openapi 1.1.4 → 1.1.6
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 +21 -0
- package/dist/OpenAPICodeSample.d.ts +9 -0
- package/dist/OpenAPICodeSample.jsx +121 -41
- package/dist/OpenAPICodeSampleInteractive.d.ts +10 -0
- package/dist/OpenAPICodeSampleInteractive.jsx +78 -0
- package/dist/OpenAPICopyButton.d.ts +4 -0
- package/dist/OpenAPICopyButton.jsx +32 -0
- package/dist/OpenAPIOperation.jsx +1 -1
- package/dist/OpenAPIPath.d.ts +1 -2
- package/dist/OpenAPIPath.jsx +32 -30
- package/dist/OpenAPIResponseExample.jsx +5 -8
- package/dist/OpenAPISchemaName.jsx +7 -6
- package/dist/OpenAPITabs.d.ts +1 -1
- package/dist/OpenAPITabs.jsx +9 -2
- package/dist/ScalarApiButton.jsx +2 -2
- package/dist/code-samples.d.ts +1 -2
- package/dist/code-samples.js +2 -2
- package/dist/generateSchemaExample.d.ts +31 -2
- package/dist/generateSchemaExample.js +307 -24
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +11 -7
- package/package.json +3 -3
- package/src/OpenAPICodeSample.tsx +181 -51
- package/src/OpenAPICodeSampleInteractive.tsx +114 -0
- package/src/OpenAPICopyButton.tsx +54 -0
- package/src/OpenAPIOperation.tsx +1 -1
- package/src/OpenAPIPath.tsx +40 -42
- package/src/OpenAPIResponseExample.tsx +30 -33
- package/src/OpenAPISchemaName.tsx +11 -6
- package/src/OpenAPITabs.tsx +13 -4
- package/src/ScalarApiButton.tsx +2 -2
- package/src/code-samples.ts +3 -3
- package/src/generateSchemaExample.ts +412 -25
- package/src/resolveOpenAPIOperation.test.ts +6 -6
- package/src/utils.ts +13 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @gitbook/react-openapi
|
|
2
2
|
|
|
3
|
+
## 1.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6eae764: Support body examples
|
|
8
|
+
- 7212973: Update scalar
|
|
9
|
+
- d2facb2: Mark properties as optional if not required
|
|
10
|
+
- 73e2b47: Fix write only properties in request example
|
|
11
|
+
- 70be2c6: Stringify default value
|
|
12
|
+
- fc00b51: Remove default value in generateSchemaExample
|
|
13
|
+
- a84b06b: Update resolveDescription and add minItems/maxItems
|
|
14
|
+
- Updated dependencies [48c18c0]
|
|
15
|
+
- Updated dependencies [7212973]
|
|
16
|
+
- @gitbook/openapi-parser@2.1.1
|
|
17
|
+
|
|
18
|
+
## 1.1.5
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 886e204: Update OpenAPI operation path design
|
|
23
|
+
|
|
3
24
|
## 1.1.4
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
1
2
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
2
3
|
/**
|
|
3
4
|
* Display code samples to execute the operation.
|
|
@@ -7,3 +8,11 @@ export declare function OpenAPICodeSample(props: {
|
|
|
7
8
|
data: OpenAPIOperationData;
|
|
8
9
|
context: OpenAPIContextProps;
|
|
9
10
|
}): import("react").JSX.Element | null;
|
|
11
|
+
export interface MediaTypeRenderer {
|
|
12
|
+
mediaType: string;
|
|
13
|
+
element: React.ReactNode;
|
|
14
|
+
examples: Array<{
|
|
15
|
+
example: OpenAPIV3.ExampleObject;
|
|
16
|
+
element: React.ReactNode;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
@@ -9,19 +9,47 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
+
import { OpenAPIMediaTypeExamplesBody, OpenAPIMediaTypeExamplesSelector, } from './OpenAPICodeSampleInteractive';
|
|
12
13
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
|
14
|
+
import { ScalarApiButton } from './ScalarApiButton';
|
|
13
15
|
import { StaticSection } from './StaticSection';
|
|
14
16
|
import { codeSampleGenerators } from './code-samples';
|
|
15
|
-
import {
|
|
17
|
+
import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
|
|
16
18
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
17
19
|
import { getDefaultServerURL } from './util/server';
|
|
18
20
|
import { checkIsReference, createStateKey } from './utils';
|
|
21
|
+
var CUSTOM_CODE_SAMPLES_KEYS = ['x-custom-examples', 'x-code-samples', 'x-codeSamples'];
|
|
19
22
|
/**
|
|
20
23
|
* Display code samples to execute the operation.
|
|
21
24
|
* It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
|
|
22
25
|
*/
|
|
23
26
|
export function OpenAPICodeSample(props) {
|
|
24
|
-
var
|
|
27
|
+
var data = props.data;
|
|
28
|
+
// If code samples are disabled at operation level, we don't display the code samples.
|
|
29
|
+
if (data.operation['x-codeSamples'] === false) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
var customCodeSamples = getCustomCodeSamples(props);
|
|
33
|
+
// If code samples are disabled at the top-level and not custom code samples are defined,
|
|
34
|
+
// we don't display the code samples.
|
|
35
|
+
if (data['x-codeSamples'] === false && !customCodeSamples) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
var samples = customCodeSamples !== null && customCodeSamples !== void 0 ? customCodeSamples : generateCodeSamples(props);
|
|
39
|
+
if (samples.length === 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return (<OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
|
|
43
|
+
<StaticSection header={<OpenAPITabsList />} className="openapi-codesample">
|
|
44
|
+
<OpenAPITabsPanels />
|
|
45
|
+
</StaticSection>
|
|
46
|
+
</OpenAPITabs>);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate code samples for the operation.
|
|
50
|
+
*/
|
|
51
|
+
function generateCodeSamples(props) {
|
|
52
|
+
var _a, _b;
|
|
25
53
|
var data = props.data, context = props.context;
|
|
26
54
|
var searchParams = new URLSearchParams();
|
|
27
55
|
var headersObject = {};
|
|
@@ -50,33 +78,92 @@ export function OpenAPICodeSample(props) {
|
|
|
50
78
|
var requestBody = !checkIsReference(data.operation.requestBody)
|
|
51
79
|
? data.operation.requestBody
|
|
52
80
|
: undefined;
|
|
53
|
-
var
|
|
54
|
-
|
|
55
|
-
:
|
|
56
|
-
var
|
|
57
|
-
var
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
var url = getDefaultServerURL(data.servers) +
|
|
82
|
+
data.path +
|
|
83
|
+
(searchParams.size ? "?".concat(searchParams.toString()) : '');
|
|
84
|
+
var genericHeaders = __assign(__assign({}, getSecurityHeaders(data.securities)), headersObject);
|
|
85
|
+
var mediaTypeRendererFactories = Object.entries((_b = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) !== null && _b !== void 0 ? _b : {}).map(function (_a) {
|
|
86
|
+
var mediaType = _a[0], mediaTypeObject = _a[1];
|
|
87
|
+
return function (generator) {
|
|
88
|
+
var mediaTypeHeaders = __assign(__assign({}, genericHeaders), { 'Content-Type': mediaType });
|
|
89
|
+
return {
|
|
90
|
+
mediaType: mediaType,
|
|
91
|
+
element: context.renderCodeBlock({
|
|
92
|
+
code: generator.generate({
|
|
93
|
+
url: url,
|
|
94
|
+
method: data.method,
|
|
95
|
+
body: undefined,
|
|
96
|
+
headers: mediaTypeHeaders,
|
|
97
|
+
}),
|
|
98
|
+
syntax: generator.syntax,
|
|
99
|
+
}),
|
|
100
|
+
examples: generateMediaTypeExamples(mediaTypeObject, {
|
|
101
|
+
mode: 'write',
|
|
102
|
+
}).map(function (example) { return ({
|
|
103
|
+
example: example,
|
|
104
|
+
element: context.renderCodeBlock({
|
|
105
|
+
code: generator.generate({
|
|
106
|
+
url: url,
|
|
107
|
+
method: data.method,
|
|
108
|
+
body: example.value,
|
|
109
|
+
headers: mediaTypeHeaders,
|
|
110
|
+
}),
|
|
111
|
+
syntax: generator.syntax,
|
|
112
|
+
}),
|
|
113
|
+
}); }),
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
return codeSampleGenerators.map(function (generator) {
|
|
118
|
+
if (mediaTypeRendererFactories.length > 0) {
|
|
119
|
+
var renderers = mediaTypeRendererFactories.map(function (generate) { return generate(generator); });
|
|
120
|
+
return {
|
|
121
|
+
key: "default-".concat(generator.id),
|
|
122
|
+
label: generator.label,
|
|
123
|
+
body: <OpenAPIMediaTypeExamplesBody data={data} renderers={renderers}/>,
|
|
124
|
+
footer: (<OpenAPICodeSampleFooter renderers={renderers} data={data} context={context}/>),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
key: "default-".concat(generator.id),
|
|
129
|
+
label: generator.label,
|
|
130
|
+
body: context.renderCodeBlock({
|
|
131
|
+
code: generator.generate({
|
|
132
|
+
url: url,
|
|
133
|
+
method: data.method,
|
|
134
|
+
body: undefined,
|
|
135
|
+
headers: genericHeaders,
|
|
136
|
+
}),
|
|
137
|
+
syntax: generator.syntax,
|
|
138
|
+
}),
|
|
139
|
+
footer: <OpenAPICodeSampleFooter data={data} renderers={[]} context={context}/>,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function OpenAPICodeSampleFooter(props) {
|
|
144
|
+
var data = props.data, context = props.context, renderers = props.renderers;
|
|
145
|
+
var method = data.method, path = data.path;
|
|
146
|
+
var specUrl = context.specUrl;
|
|
147
|
+
var hideTryItPanel = data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'];
|
|
148
|
+
var hasMediaTypes = renderers.length > 0;
|
|
149
|
+
if (hideTryItPanel && !hasMediaTypes) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
if (!validateHttpMethod(method)) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return (<div className="openapi-codesample-footer">
|
|
156
|
+
{hasMediaTypes ? (<OpenAPIMediaTypeExamplesSelector data={data} renderers={renderers}/>) : (<span />)}
|
|
157
|
+
{!hideTryItPanel && <ScalarApiButton method={method} path={path} specUrl={specUrl}/>}
|
|
158
|
+
</div>);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get custom code samples for the operation.
|
|
162
|
+
*/
|
|
163
|
+
function getCustomCodeSamples(props) {
|
|
164
|
+
var data = props.data, context = props.context;
|
|
78
165
|
var customCodeSamples = null;
|
|
79
|
-
|
|
166
|
+
CUSTOM_CODE_SAMPLES_KEYS.forEach(function (key) {
|
|
80
167
|
var customSamples = data.operation[key];
|
|
81
168
|
if (customSamples && Array.isArray(customSamples)) {
|
|
82
169
|
customCodeSamples = customSamples
|
|
@@ -86,27 +173,17 @@ export function OpenAPICodeSample(props) {
|
|
|
86
173
|
typeof sample.lang === 'string');
|
|
87
174
|
})
|
|
88
175
|
.map(function (sample, index) { return ({
|
|
89
|
-
key: "
|
|
176
|
+
key: "custom-sample-".concat(sample.lang, "-").concat(index),
|
|
90
177
|
label: sample.label,
|
|
91
178
|
body: context.renderCodeBlock({
|
|
92
179
|
code: sample.source,
|
|
93
180
|
syntax: sample.lang,
|
|
94
181
|
}),
|
|
182
|
+
footer: (<OpenAPICodeSampleFooter renderers={[]} data={data} context={context}/>),
|
|
95
183
|
}); });
|
|
96
184
|
}
|
|
97
185
|
});
|
|
98
|
-
|
|
99
|
-
// If code samples are defined at the operation level, it will override the top-level setting
|
|
100
|
-
var codeSamplesDisabled = data['x-codeSamples'] === false || data.operation['x-codeSamples'] === false;
|
|
101
|
-
var samples = customCodeSamples !== null && customCodeSamples !== void 0 ? customCodeSamples : (!codeSamplesDisabled ? autoCodeSamples : []);
|
|
102
|
-
if (samples.length === 0) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
return (<OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
|
|
106
|
-
<StaticSection header={<OpenAPITabsList />} className="openapi-codesample">
|
|
107
|
-
<OpenAPITabsPanels />
|
|
108
|
-
</StaticSection>
|
|
109
|
-
</OpenAPITabs>);
|
|
186
|
+
return customCodeSamples;
|
|
110
187
|
}
|
|
111
188
|
function getSecurityHeaders(securities) {
|
|
112
189
|
var _a;
|
|
@@ -146,3 +223,6 @@ function getSecurityHeaders(securities) {
|
|
|
146
223
|
}
|
|
147
224
|
}
|
|
148
225
|
}
|
|
226
|
+
function validateHttpMethod(method) {
|
|
227
|
+
return ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'].includes(method);
|
|
228
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MediaTypeRenderer } from './OpenAPICodeSample';
|
|
2
|
+
import type { OpenAPIOperationData } from './types';
|
|
3
|
+
export declare function OpenAPIMediaTypeExamplesSelector(props: {
|
|
4
|
+
data: OpenAPIOperationData;
|
|
5
|
+
renderers: MediaTypeRenderer[];
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function OpenAPIMediaTypeExamplesBody(props: {
|
|
8
|
+
data: OpenAPIOperationData;
|
|
9
|
+
renderers: MediaTypeRenderer[];
|
|
10
|
+
}): string | number | boolean | Iterable<import("react").ReactNode> | import("react").JSX.Element | null | undefined;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useStore } from 'zustand';
|
|
5
|
+
import { getOrCreateTabStoreByKey } from './useSyncedTabsGlobalState';
|
|
6
|
+
function useMediaTypeState(data, defaultKey) {
|
|
7
|
+
var method = data.method, path = data.path;
|
|
8
|
+
var store = useStore(getOrCreateTabStoreByKey("media-type-".concat(method, "-").concat(path), defaultKey));
|
|
9
|
+
if (typeof store.tabKey !== 'string') {
|
|
10
|
+
throw new Error('Media type key is not a string');
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
mediaType: store.tabKey,
|
|
14
|
+
setMediaType: useCallback(function (index) { return store.setTabKey(index); }, [store.setTabKey]),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function useMediaTypeSampleIndexState(data, mediaType) {
|
|
18
|
+
var method = data.method, path = data.path;
|
|
19
|
+
var store = useStore(getOrCreateTabStoreByKey("media-type-sample-".concat(mediaType, "-").concat(method, "-").concat(path), 0));
|
|
20
|
+
if (typeof store.tabKey !== 'number') {
|
|
21
|
+
throw new Error('Example key is not a number');
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
index: store.tabKey,
|
|
25
|
+
setIndex: useCallback(function (index) { return store.setTabKey(index); }, [store.setTabKey]),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function OpenAPIMediaTypeExamplesSelector(props) {
|
|
29
|
+
var data = props.data, renderers = props.renderers;
|
|
30
|
+
if (!renderers[0]) {
|
|
31
|
+
throw new Error('No renderers provided');
|
|
32
|
+
}
|
|
33
|
+
var state = useMediaTypeState(data, renderers[0].mediaType);
|
|
34
|
+
var selected = renderers.find(function (r) { return r.mediaType === state.mediaType; }) || renderers[0];
|
|
35
|
+
return (<div className="openapi-codesample-selectors">
|
|
36
|
+
<select className={clsx('openapi-select')} value={state.mediaType} onChange={function (e) { return state.setMediaType(e.target.value); }}>
|
|
37
|
+
{renderers.map(function (renderer) { return (<option key={renderer.mediaType} value={renderer.mediaType}>
|
|
38
|
+
{renderer.mediaType}
|
|
39
|
+
</option>); })}
|
|
40
|
+
</select>
|
|
41
|
+
<ExamplesSelector data={data} renderer={selected}/>
|
|
42
|
+
</div>);
|
|
43
|
+
}
|
|
44
|
+
function ExamplesSelector(props) {
|
|
45
|
+
var data = props.data, renderer = props.renderer;
|
|
46
|
+
var state = useMediaTypeSampleIndexState(data, renderer.mediaType);
|
|
47
|
+
if (renderer.examples.length < 2) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return (<select className={clsx('openapi-select')} value={String(state.index)} onChange={function (e) { return state.setIndex(Number(e.target.value)); }}>
|
|
51
|
+
{renderer.examples.map(function (example, index) { return (<option key={index} value={index}>
|
|
52
|
+
{example.example.summary || "Example ".concat(index + 1)}
|
|
53
|
+
</option>); })}
|
|
54
|
+
</select>);
|
|
55
|
+
}
|
|
56
|
+
export function OpenAPIMediaTypeExamplesBody(props) {
|
|
57
|
+
var _a;
|
|
58
|
+
var renderers = props.renderers, data = props.data;
|
|
59
|
+
if (!renderers[0]) {
|
|
60
|
+
throw new Error('No renderers provided');
|
|
61
|
+
}
|
|
62
|
+
var mediaTypeState = useMediaTypeState(data, renderers[0].mediaType);
|
|
63
|
+
var selected = (_a = renderers.find(function (r) { return r.mediaType === mediaTypeState.mediaType; })) !== null && _a !== void 0 ? _a : renderers[0];
|
|
64
|
+
if (selected.examples.length === 0) {
|
|
65
|
+
return selected.element;
|
|
66
|
+
}
|
|
67
|
+
return <ExamplesBody data={data} renderer={selected}/>;
|
|
68
|
+
}
|
|
69
|
+
function ExamplesBody(props) {
|
|
70
|
+
var _a;
|
|
71
|
+
var data = props.data, renderer = props.renderer;
|
|
72
|
+
var exampleState = useMediaTypeSampleIndexState(data, renderer.mediaType);
|
|
73
|
+
var example = (_a = renderer.examples[exampleState.index]) !== null && _a !== void 0 ? _a : renderer.examples[0];
|
|
74
|
+
if (!example) {
|
|
75
|
+
throw new Error("No example found for index ".concat(exampleState.index));
|
|
76
|
+
}
|
|
77
|
+
return example.element;
|
|
78
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Button, Tooltip, TooltipTrigger } from 'react-aria-components';
|
|
4
|
+
export function OpenAPICopyButton(props) {
|
|
5
|
+
var value = props.value;
|
|
6
|
+
var children = props.children, onPress = props.onPress, className = props.className;
|
|
7
|
+
var _a = useState(false), copied = _a[0], setCopied = _a[1];
|
|
8
|
+
var _b = useState(false), isOpen = _b[0], setIsOpen = _b[1];
|
|
9
|
+
var handleCopy = function () {
|
|
10
|
+
if (!value)
|
|
11
|
+
return;
|
|
12
|
+
navigator.clipboard.writeText(value).then(function () {
|
|
13
|
+
setIsOpen(true);
|
|
14
|
+
setCopied(true);
|
|
15
|
+
setTimeout(function () {
|
|
16
|
+
setCopied(false);
|
|
17
|
+
}, 2000);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
return (<TooltipTrigger isOpen={isOpen} onOpenChange={setIsOpen} closeDelay={200} delay={200}>
|
|
21
|
+
<Button type="button" preventFocusOnPress onPress={function (e) {
|
|
22
|
+
handleCopy();
|
|
23
|
+
onPress === null || onPress === void 0 ? void 0 : onPress(e);
|
|
24
|
+
}} className={"openapi-copy-button ".concat(className)} {...props}>
|
|
25
|
+
{children}
|
|
26
|
+
</Button>
|
|
27
|
+
|
|
28
|
+
<Tooltip isOpen={isOpen} onOpenChange={setIsOpen} placement="top" offset={4} className="openapi-tooltip">
|
|
29
|
+
{copied ? 'Copied' : 'Copy to clipboard'}{' '}
|
|
30
|
+
</Tooltip>
|
|
31
|
+
</TooltipTrigger>);
|
|
32
|
+
}
|
|
@@ -25,6 +25,7 @@ export function OpenAPIOperation(props) {
|
|
|
25
25
|
title: operation.summary,
|
|
26
26
|
})
|
|
27
27
|
: null}
|
|
28
|
+
<OpenAPIPath data={data} context={context}/>
|
|
28
29
|
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
|
|
29
30
|
</div>
|
|
30
31
|
<div className="openapi-columns">
|
|
@@ -37,7 +38,6 @@ export function OpenAPIOperation(props) {
|
|
|
37
38
|
{'.'}
|
|
38
39
|
</div>) : null}
|
|
39
40
|
<OpenAPIOperationDescription operation={operation} context={context}/>
|
|
40
|
-
<OpenAPIPath data={data} context={context}/>
|
|
41
41
|
<OpenAPISpec data={data} context={clientContext}/>
|
|
42
42
|
</div>
|
|
43
43
|
<div className="openapi-column-preview">
|
package/dist/OpenAPIPath.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
1
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
3
2
|
/**
|
|
4
3
|
* Display the path of an operation.
|
|
@@ -6,4 +5,4 @@ import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
|
6
5
|
export declare function OpenAPIPath(props: {
|
|
7
6
|
data: OpenAPIOperationData;
|
|
8
7
|
context: OpenAPIContextProps;
|
|
9
|
-
}):
|
|
8
|
+
}): import("react").JSX.Element;
|
package/dist/OpenAPIPath.jsx
CHANGED
|
@@ -1,47 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
|
2
|
+
import { getDefaultServerURL } from './util/server';
|
|
2
3
|
/**
|
|
3
4
|
* Display the path of an operation.
|
|
4
5
|
*/
|
|
5
6
|
export function OpenAPIPath(props) {
|
|
6
|
-
var data = props.data
|
|
7
|
-
var method = data.method, path = data.path;
|
|
8
|
-
var
|
|
9
|
-
var
|
|
7
|
+
var data = props.data;
|
|
8
|
+
var method = data.method, path = data.path, operation = data.operation;
|
|
9
|
+
var server = getDefaultServerURL(data.servers);
|
|
10
|
+
var formattedPath = formatPath(path);
|
|
10
11
|
return (<div className="openapi-path">
|
|
11
12
|
<div className={"openapi-method openapi-method-".concat(method)}>{method}</div>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
|
|
14
|
+
<OpenAPICopyButton value={server + path} className="openapi-path-title" data-deprecated={operation.deprecated}>
|
|
15
|
+
<span className="openapi-path-server">{server}</span>
|
|
16
|
+
{formattedPath}
|
|
17
|
+
</OpenAPICopyButton>
|
|
16
18
|
</div>);
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Format the path to highlight placeholders
|
|
20
|
+
/**
|
|
21
|
+
* Format the path by wrapping placeholders in <span> tags.
|
|
22
|
+
*/
|
|
22
23
|
function formatPath(path) {
|
|
23
24
|
// Matches placeholders like {id}, {userId}, etc.
|
|
24
|
-
var regex = /\{(\w+)\}
|
|
25
|
+
var regex = /\{\s*(\w+)\s*\}|:\w+/g;
|
|
25
26
|
var parts = [];
|
|
26
27
|
var lastIndex = 0;
|
|
27
|
-
//
|
|
28
|
-
path.replace(regex, function (match,
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
//Wrap the variables in <span> tags and maintain either {variable} or :variable
|
|
29
|
+
path.replace(regex, function (match, _, offset) {
|
|
30
|
+
if (offset > lastIndex) {
|
|
31
|
+
parts.push(path.slice(lastIndex, offset));
|
|
32
|
+
}
|
|
33
|
+
parts.push(<span key={offset} className="openapi-path-variable">
|
|
34
|
+
{match}
|
|
35
|
+
</span>);
|
|
31
36
|
lastIndex = offset + match.length;
|
|
32
37
|
return match;
|
|
33
38
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
var formattedPath = parts.
|
|
38
|
-
if (typeof part === 'string'
|
|
39
|
-
|
|
40
|
-
/
|
|
41
|
-
</span>);
|
|
39
|
+
if (lastIndex < path.length) {
|
|
40
|
+
parts.push(path.slice(lastIndex));
|
|
41
|
+
}
|
|
42
|
+
var formattedPath = parts.map(function (part, index) {
|
|
43
|
+
if (typeof part === 'string') {
|
|
44
|
+
return <span key={index}>{part}</span>;
|
|
42
45
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return <span>{formattedPath}</span>;
|
|
46
|
+
return part;
|
|
47
|
+
});
|
|
48
|
+
return formattedPath;
|
|
47
49
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Markdown } from './Markdown';
|
|
1
2
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
|
2
3
|
import { StaticSection } from './StaticSection';
|
|
3
4
|
import { generateSchemaExample } from './generateSchemaExample';
|
|
@@ -32,35 +33,31 @@ export function OpenAPIResponseExample(props) {
|
|
|
32
33
|
}
|
|
33
34
|
return Number(a) - Number(b);
|
|
34
35
|
});
|
|
35
|
-
var tabs = responses
|
|
36
|
-
.map(function (_a) {
|
|
36
|
+
var tabs = responses.map(function (_a) {
|
|
37
37
|
var key = _a[0], responseObject = _a[1];
|
|
38
38
|
var description = resolveDescription(responseObject);
|
|
39
39
|
if (checkIsReference(responseObject)) {
|
|
40
40
|
return {
|
|
41
41
|
key: key,
|
|
42
42
|
label: key,
|
|
43
|
-
description: description,
|
|
44
43
|
body: (<OpenAPIExample example={getExampleFromReference(responseObject)} context={context} syntax="json"/>),
|
|
44
|
+
footer: description ? <Markdown source={description}/> : undefined,
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
|
|
48
48
|
return {
|
|
49
49
|
key: key,
|
|
50
50
|
label: key,
|
|
51
|
-
description: description,
|
|
52
51
|
body: <OpenAPIEmptyResponseExample />,
|
|
52
|
+
footer: description ? <Markdown source={description}/> : undefined,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
return {
|
|
56
56
|
key: key,
|
|
57
57
|
label: key,
|
|
58
|
-
description: resolveDescription(responseObject),
|
|
59
58
|
body: <OpenAPIResponse context={context} content={responseObject.content}/>,
|
|
59
|
+
footer: description ? <Markdown source={description}/> : undefined,
|
|
60
60
|
};
|
|
61
|
-
})
|
|
62
|
-
.filter(function (val) {
|
|
63
|
-
return Boolean(val);
|
|
64
61
|
});
|
|
65
62
|
if (tabs.length === 0) {
|
|
66
63
|
return null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
1
2
|
/**
|
|
2
3
|
* Display the schema name row.
|
|
3
4
|
* It includes the property name, type, required and deprecated status.
|
|
@@ -14,21 +15,21 @@ export function OpenAPISchemaName(props) {
|
|
|
14
15
|
{additionalItems ? (<span className="openapi-schema-type">{additionalItems}</span>) : null}
|
|
15
16
|
</span>
|
|
16
17
|
{(schema === null || schema === void 0 ? void 0 : schema.readOnly) ? <span className="openapi-schema-readonly">read-only</span> : null}
|
|
17
|
-
{required ? <span className="openapi-schema-required">required</span> :
|
|
18
|
+
{required ? (<span className="openapi-schema-required">required</span>) : (<span className="openapi-schema-optional">optional</span>)}
|
|
18
19
|
{(schema === null || schema === void 0 ? void 0 : schema.deprecated) ? <span className="openapi-deprecated">Deprecated</span> : null}
|
|
19
20
|
</div>);
|
|
20
21
|
}
|
|
21
22
|
function getAdditionalItems(schema) {
|
|
22
23
|
var additionalItems = '';
|
|
23
|
-
if (schema.minimum || schema.minLength) {
|
|
24
|
-
additionalItems += " \u00B7 min: ".concat(schema.minimum || schema.minLength);
|
|
24
|
+
if (schema.minimum || schema.minLength || schema.minItems) {
|
|
25
|
+
additionalItems += " \u00B7 min: ".concat(schema.minimum || schema.minLength || schema.minItems);
|
|
25
26
|
}
|
|
26
|
-
if (schema.maximum || schema.maxLength) {
|
|
27
|
-
additionalItems += " \u00B7 max: ".concat(schema.maximum || schema.maxLength);
|
|
27
|
+
if (schema.maximum || schema.maxLength || schema.maxItems) {
|
|
28
|
+
additionalItems += " \u00B7 max: ".concat(schema.maximum || schema.maxLength || schema.maxItems);
|
|
28
29
|
}
|
|
29
30
|
// If the schema has a default value, we display it
|
|
30
31
|
if (typeof schema.default !== 'undefined') {
|
|
31
|
-
additionalItems += " \u00B7 default: ".concat(schema.default);
|
|
32
|
+
additionalItems += " \u00B7 default: ".concat(stringifyOpenAPI(schema.default));
|
|
32
33
|
}
|
|
33
34
|
if (schema.nullable) {
|
|
34
35
|
additionalItems = ' | nullable';
|
package/dist/OpenAPITabs.d.ts
CHANGED
package/dist/OpenAPITabs.jsx
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
|
|
4
4
|
import { useEventCallback } from 'usehooks-ts';
|
|
5
|
-
import { Markdown } from './Markdown';
|
|
6
5
|
import { getOrCreateTabStoreByKey } from './useSyncedTabsGlobalState';
|
|
7
6
|
var OpenAPITabsContext = createContext(null);
|
|
8
7
|
function useOpenAPITabsContext() {
|
|
@@ -105,6 +104,14 @@ export function OpenAPITabsPanels() {
|
|
|
105
104
|
var key = selectedTab.key.toString();
|
|
106
105
|
return (<TabPanel key={key} id={key} className="openapi-tabs-panel">
|
|
107
106
|
{selectedTab.body}
|
|
108
|
-
{selectedTab.
|
|
107
|
+
{selectedTab.footer ? (<OpenAPITabsPanelFooter>{selectedTab.footer}</OpenAPITabsPanelFooter>) : null}
|
|
109
108
|
</TabPanel>);
|
|
110
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* The OpenAPI Tabs panel footer component.
|
|
112
|
+
* This component should be used as a child of the OpenAPITabs component.
|
|
113
|
+
*/
|
|
114
|
+
function OpenAPITabsPanelFooter(props) {
|
|
115
|
+
var children = props.children;
|
|
116
|
+
return <div className="openapi-tabs-footer">{children}</div>;
|
|
117
|
+
}
|
package/dist/ScalarApiButton.jsx
CHANGED
|
@@ -16,10 +16,10 @@ export function ScalarApiButton(props) {
|
|
|
16
16
|
(_b = (_a = controllerRef.current) === null || _a === void 0 ? void 0 : _a.openClient) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
17
17
|
setIsOpen(true);
|
|
18
18
|
}}>
|
|
19
|
-
|
|
19
|
+
Test it
|
|
20
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 12" fill="currentColor">
|
|
20
21
|
<path stroke="currentColor" strokeWidth="1.5" d="M1 10.05V1.43c0-.2.2-.31.37-.22l7.26 4.08c.17.1.17.33.01.43l-7.26 4.54a.25.25 0 0 1-.38-.21Z"/>
|
|
21
22
|
</svg>
|
|
22
|
-
Test it
|
|
23
23
|
</button>
|
|
24
24
|
|
|
25
25
|
{isOpen &&
|
package/dist/code-samples.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface CodeSampleInput {
|
|
|
4
4
|
headers?: Record<string, string>;
|
|
5
5
|
body?: any;
|
|
6
6
|
}
|
|
7
|
-
interface CodeSampleGenerator {
|
|
7
|
+
export interface CodeSampleGenerator {
|
|
8
8
|
id: string;
|
|
9
9
|
label: string;
|
|
10
10
|
syntax: string;
|
|
@@ -15,4 +15,3 @@ export declare function parseHostAndPath(url: string): {
|
|
|
15
15
|
host: string | undefined;
|
|
16
16
|
path: string;
|
|
17
17
|
};
|
|
18
|
-
export {};
|
package/dist/code-samples.js
CHANGED
|
@@ -203,7 +203,7 @@ var BodyGenerators = {
|
|
|
203
203
|
}
|
|
204
204
|
else if (isXML(contentType) || isCSV(contentType)) {
|
|
205
205
|
// We use --data-binary to avoid cURL converting newlines to \r\n
|
|
206
|
-
body = "--data-binary $'".concat(stringifyOpenAPI(body).replace(/"/g, ''), "'");
|
|
206
|
+
body = "--data-binary $'".concat(stringifyOpenAPI(body).replace(/"/g, '').replace(/\\n/g, '\n'), "'");
|
|
207
207
|
}
|
|
208
208
|
else if (isGraphQL(contentType)) {
|
|
209
209
|
body = "--data '".concat(stringifyOpenAPI(body), "'");
|
|
@@ -215,7 +215,7 @@ var BodyGenerators = {
|
|
|
215
215
|
body = "--data-binary '@".concat(String(body), "'");
|
|
216
216
|
}
|
|
217
217
|
else {
|
|
218
|
-
body = "--data '".concat(stringifyOpenAPI(body, null, 2), "'");
|
|
218
|
+
body = "--data '".concat(stringifyOpenAPI(body, null, 2).replace(/\\n/g, '\n'), "'");
|
|
219
219
|
}
|
|
220
220
|
return {
|
|
221
221
|
body: body,
|