docusaurus-theme-openapi-docs 4.5.1 → 4.7.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/lib/markdown/schema.js +14 -1
- package/lib/theme/ApiExplorer/Accept/slice.d.ts +5 -2
- package/lib/theme/ApiExplorer/Authorization/index.js +50 -9
- package/lib/theme/ApiExplorer/Authorization/slice.d.ts +145 -3
- package/lib/theme/ApiExplorer/Authorization/slice.js +3 -1
- package/lib/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.d.ts +7 -0
- package/lib/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.js +126 -0
- package/lib/theme/ApiExplorer/Body/FormBodyItem/index.d.ts +9 -0
- package/lib/theme/ApiExplorer/Body/FormBodyItem/index.js +110 -0
- package/lib/theme/ApiExplorer/Body/index.js +322 -193
- package/lib/theme/ApiExplorer/Body/resolveSchemaWithSelections.d.ts +13 -0
- package/lib/theme/ApiExplorer/Body/resolveSchemaWithSelections.js +133 -0
- package/lib/theme/ApiExplorer/Body/slice.d.ts +1056 -11
- package/lib/theme/ApiExplorer/Body/slice.js +22 -2
- package/lib/theme/ApiExplorer/CodeSnippets/index.d.ts +2 -1
- package/lib/theme/ApiExplorer/CodeSnippets/index.js +37 -26
- package/lib/theme/ApiExplorer/CodeTabs/_CodeTabs.scss +5 -1
- package/lib/theme/ApiExplorer/CodeTabs/index.d.ts +3 -3
- package/lib/theme/ApiExplorer/CodeTabs/index.js +2 -2
- package/lib/theme/ApiExplorer/ContentType/slice.d.ts +5 -2
- package/lib/theme/ApiExplorer/FormFileUpload/index.js +6 -1
- package/lib/theme/ApiExplorer/FormItem/index.js +6 -1
- package/lib/theme/ApiExplorer/FormTextInput/index.d.ts +2 -0
- package/lib/theme/ApiExplorer/FormTextInput/index.js +8 -1
- package/lib/theme/ApiExplorer/LiveEditor/index.js +11 -4
- package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.js +15 -5
- package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js +11 -3
- package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.js +12 -4
- package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.js +11 -2
- package/lib/theme/ApiExplorer/ParamOptions/index.js +11 -1
- package/lib/theme/ApiExplorer/ParamOptions/slice.d.ts +0 -4
- package/lib/theme/ApiExplorer/ParamOptions/slice.js +4 -4
- package/lib/theme/ApiExplorer/Request/index.js +110 -17
- package/lib/theme/ApiExplorer/Request/makeRequest.d.ts +7 -1
- package/lib/theme/ApiExplorer/Request/makeRequest.js +94 -24
- package/lib/theme/ApiExplorer/Response/index.js +34 -14
- package/lib/theme/ApiExplorer/Response/slice.d.ts +31 -7
- package/lib/theme/ApiExplorer/SchemaSelection/index.d.ts +2 -0
- package/lib/theme/ApiExplorer/SchemaSelection/index.js +36 -0
- package/lib/theme/ApiExplorer/SchemaSelection/slice.d.ts +37 -0
- package/lib/theme/ApiExplorer/SchemaSelection/slice.js +39 -0
- package/lib/theme/ApiExplorer/SecuritySchemes/index.js +208 -69
- package/lib/theme/ApiExplorer/Server/index.js +16 -2
- package/lib/theme/ApiExplorer/Server/slice.d.ts +49 -3
- package/lib/theme/ApiExplorer/buildPostmanRequest.js +46 -57
- package/lib/theme/ApiExplorer/index.js +4 -0
- package/lib/theme/ApiExplorer/persistenceMiddleware.d.ts +21 -0
- package/lib/theme/ApiExplorer/{persistanceMiddleware.js → persistenceMiddleware.js} +16 -9
- package/lib/theme/ApiExplorer/storage-utils.d.ts +2 -2
- package/lib/theme/ApiExplorer/storage-utils.js +3 -3
- package/lib/theme/ApiItem/Layout/index.d.ts +1 -1
- package/lib/theme/ApiItem/hooks.d.ts +10 -9
- package/lib/theme/ApiItem/index.js +13 -8
- package/lib/theme/ApiItem/store.d.ts +61 -43
- package/lib/theme/ApiItem/store.js +6 -2
- package/lib/theme/ApiTabs/index.js +6 -1
- package/lib/theme/Example/_Example.scss +11 -0
- package/lib/theme/Example/index.d.ts +24 -0
- package/lib/theme/Example/index.js +170 -0
- package/lib/theme/ParamsDetails/index.js +9 -1
- package/lib/theme/ParamsItem/index.d.ts +1 -1
- package/lib/theme/ParamsItem/index.js +43 -74
- package/lib/theme/RequestSchema/index.js +68 -48
- package/lib/theme/ResponseExamples/index.js +23 -3
- package/lib/theme/ResponseSchema/index.js +97 -82
- package/lib/theme/Schema/index.d.ts +6 -0
- package/lib/theme/Schema/index.js +240 -31
- package/lib/theme/SchemaItem/index.js +64 -36
- package/lib/theme/SchemaTabs/index.d.ts +8 -1
- package/lib/theme/SchemaTabs/index.js +14 -2
- package/lib/theme/StatusCodes/index.d.ts +1 -1
- package/lib/theme/StatusCodes/index.js +11 -2
- package/lib/theme/styles.scss +15 -0
- package/lib/theme/translationIds.d.ts +90 -0
- package/lib/theme/translationIds.js +114 -0
- package/package.json +28 -28
- package/src/markdown/schema.ts +17 -1
- package/src/theme/ApiExplorer/Authorization/index.tsx +51 -10
- package/src/theme/ApiExplorer/Authorization/slice.ts +1 -1
- package/src/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.tsx +77 -0
- package/src/theme/ApiExplorer/Body/FormBodyItem/index.tsx +120 -0
- package/src/theme/ApiExplorer/Body/index.tsx +262 -198
- package/{lib/types.js → src/theme/ApiExplorer/Body/json2xml.d.ts} +2 -2
- package/src/theme/ApiExplorer/Body/resolveSchemaWithSelections.ts +155 -0
- package/src/theme/ApiExplorer/Body/slice.ts +40 -1
- package/src/theme/ApiExplorer/CodeSnippets/index.tsx +43 -29
- package/src/theme/ApiExplorer/CodeTabs/_CodeTabs.scss +5 -1
- package/src/theme/ApiExplorer/CodeTabs/index.tsx +6 -5
- package/src/theme/ApiExplorer/ContentType/index.tsx +1 -1
- package/src/theme/ApiExplorer/FormFileUpload/index.tsx +6 -1
- package/src/theme/ApiExplorer/FormItem/index.tsx +8 -1
- package/src/theme/ApiExplorer/FormTextInput/index.tsx +10 -1
- package/src/theme/ApiExplorer/LiveEditor/index.tsx +11 -4
- package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.tsx +16 -6
- package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.tsx +12 -4
- package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx +12 -4
- package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx +12 -3
- package/src/theme/ApiExplorer/ParamOptions/index.tsx +10 -2
- package/src/theme/ApiExplorer/ParamOptions/slice.ts +1 -1
- package/src/theme/ApiExplorer/Request/index.tsx +108 -17
- package/src/theme/ApiExplorer/Request/makeRequest.ts +106 -25
- package/src/theme/ApiExplorer/Response/index.tsx +30 -8
- package/src/theme/ApiExplorer/SchemaSelection/index.ts +13 -0
- package/src/theme/ApiExplorer/SchemaSelection/slice.ts +46 -0
- package/src/theme/ApiExplorer/SecuritySchemes/index.tsx +157 -69
- package/src/theme/ApiExplorer/Server/index.tsx +12 -4
- package/src/theme/ApiExplorer/buildPostmanRequest.ts +47 -63
- package/src/theme/ApiExplorer/index.tsx +5 -0
- package/src/theme/ApiExplorer/{persistanceMiddleware.ts → persistenceMiddleware.ts} +23 -13
- package/src/theme/ApiExplorer/storage-utils.ts +4 -4
- package/src/theme/ApiItem/Layout/index.tsx +1 -1
- package/src/theme/ApiItem/index.tsx +13 -7
- package/src/theme/ApiItem/store.ts +2 -0
- package/src/theme/ApiTabs/index.tsx +6 -1
- package/src/theme/Example/_Example.scss +11 -0
- package/src/theme/Example/index.tsx +168 -0
- package/src/theme/Markdown/index.d.ts +8 -0
- package/src/theme/ParamsDetails/index.tsx +10 -1
- package/src/theme/ParamsItem/index.tsx +38 -54
- package/src/theme/RequestSchema/index.tsx +60 -35
- package/src/theme/ResponseExamples/index.tsx +23 -3
- package/src/theme/ResponseSchema/index.tsx +73 -61
- package/src/theme/Schema/index.tsx +307 -55
- package/src/theme/SchemaItem/index.tsx +51 -33
- package/src/theme/SchemaTabs/index.tsx +19 -5
- package/src/theme/StatusCodes/index.tsx +13 -3
- package/src/theme/styles.scss +15 -0
- package/src/theme/translationIds.ts +111 -0
- package/src/theme-openapi.d.ts +7 -275
- package/src/{types.ts → types.d.ts} +9 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/theme/ApiExplorer/persistanceMiddleware.d.ts +0 -3
- package/lib/types.d.ts +0 -46
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
import React from "react";
|
|
9
9
|
|
|
10
|
+
import { translate } from "@docusaurus/Translate";
|
|
10
11
|
import { ErrorMessage } from "@hookform/error-message";
|
|
11
12
|
import FormSelect from "@theme/ApiExplorer/FormSelect";
|
|
12
13
|
import { Param, setParam } from "@theme/ApiExplorer/ParamOptions/slice";
|
|
13
14
|
import { useTypedDispatch } from "@theme/ApiItem/hooks";
|
|
15
|
+
import { OPENAPI_FORM } from "@theme/translationIds";
|
|
14
16
|
import { Controller, useFormContext } from "react-hook-form";
|
|
15
17
|
|
|
16
18
|
export interface ParamProps {
|
|
@@ -33,12 +35,19 @@ export default function ParamSelectFormItem({ param }: ParamProps) {
|
|
|
33
35
|
<>
|
|
34
36
|
<Controller
|
|
35
37
|
control={control}
|
|
36
|
-
rules={{
|
|
38
|
+
rules={{
|
|
39
|
+
required: param.required
|
|
40
|
+
? translate({
|
|
41
|
+
id: OPENAPI_FORM.FIELD_REQUIRED,
|
|
42
|
+
message: "This field is required",
|
|
43
|
+
})
|
|
44
|
+
: false,
|
|
45
|
+
}}
|
|
37
46
|
name="paramSelect"
|
|
38
|
-
render={({ field: { onChange
|
|
47
|
+
render={({ field: { onChange } }) => (
|
|
39
48
|
<FormSelect
|
|
40
49
|
options={["---", ...(options as string[])]}
|
|
41
|
-
onChange={(e: React.ChangeEvent<
|
|
50
|
+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
42
51
|
const val = e.target.value;
|
|
43
52
|
dispatch(
|
|
44
53
|
setParam({
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import React, { useState } from "react";
|
|
9
9
|
|
|
10
|
+
import { translate } from "@docusaurus/Translate";
|
|
10
11
|
import FormItem from "@theme/ApiExplorer/FormItem";
|
|
11
12
|
import ParamArrayFormItem from "@theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem";
|
|
12
13
|
import ParamBooleanFormItem from "@theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem";
|
|
@@ -14,6 +15,7 @@ import ParamMultiSelectFormItem from "@theme/ApiExplorer/ParamOptions/ParamFormI
|
|
|
14
15
|
import ParamSelectFormItem from "@theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem";
|
|
15
16
|
import ParamTextFormItem from "@theme/ApiExplorer/ParamOptions/ParamFormItems/ParamTextFormItem";
|
|
16
17
|
import { useTypedSelector } from "@theme/ApiItem/hooks";
|
|
18
|
+
import { OPENAPI_PARAM_OPTIONS } from "@theme/translationIds";
|
|
17
19
|
|
|
18
20
|
import { Param } from "./slice";
|
|
19
21
|
|
|
@@ -119,8 +121,14 @@ function ParamOptions() {
|
|
|
119
121
|
</span>
|
|
120
122
|
</span>
|
|
121
123
|
{showOptional
|
|
122
|
-
?
|
|
123
|
-
|
|
124
|
+
? translate({
|
|
125
|
+
id: OPENAPI_PARAM_OPTIONS.HIDE_OPTIONAL,
|
|
126
|
+
message: "Hide optional parameters",
|
|
127
|
+
})
|
|
128
|
+
: translate({
|
|
129
|
+
id: OPENAPI_PARAM_OPTIONS.SHOW_OPTIONAL,
|
|
130
|
+
message: "Show optional parameters",
|
|
131
|
+
})}
|
|
124
132
|
</button>
|
|
125
133
|
|
|
126
134
|
<div
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import React, { useState } from "react";
|
|
10
10
|
|
|
11
11
|
import { useDoc } from "@docusaurus/plugin-content-docs/client";
|
|
12
|
+
import { translate } from "@docusaurus/Translate";
|
|
13
|
+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
12
14
|
import Accept from "@theme/ApiExplorer/Accept";
|
|
13
15
|
import Authorization from "@theme/ApiExplorer/Authorization";
|
|
14
16
|
import Body from "@theme/ApiExplorer/Body";
|
|
@@ -24,17 +26,25 @@ import {
|
|
|
24
26
|
} from "@theme/ApiExplorer/Response/slice";
|
|
25
27
|
import Server from "@theme/ApiExplorer/Server";
|
|
26
28
|
import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
|
|
29
|
+
import { OPENAPI_REQUEST } from "@theme/translationIds";
|
|
27
30
|
import { ParameterObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
|
|
28
31
|
import { ApiItem } from "docusaurus-plugin-openapi-docs/src/types";
|
|
32
|
+
import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types";
|
|
29
33
|
import * as sdk from "postman-collection";
|
|
30
34
|
import { FormProvider, useForm } from "react-hook-form";
|
|
31
35
|
|
|
32
|
-
import makeRequest from "./makeRequest";
|
|
36
|
+
import makeRequest, { RequestError, RequestErrorType } from "./makeRequest";
|
|
33
37
|
|
|
34
38
|
function Request({ item }: { item: ApiItem }) {
|
|
35
39
|
const postman = new sdk.Request(item.postman);
|
|
36
40
|
const metadata = useDoc();
|
|
37
|
-
const { proxy, hide_send_button: hideSendButton } =
|
|
41
|
+
const { proxy: frontMatterProxy, hide_send_button: hideSendButton } =
|
|
42
|
+
metadata.frontMatter;
|
|
43
|
+
const { siteConfig } = useDocusaurusContext();
|
|
44
|
+
const themeConfig = siteConfig.themeConfig as ThemeConfig;
|
|
45
|
+
const requestTimeout = themeConfig.api?.requestTimeout;
|
|
46
|
+
// Frontmatter proxy (per-spec) takes precedence over theme config proxy (site-wide)
|
|
47
|
+
const proxy = frontMatterProxy ?? themeConfig.api?.proxy;
|
|
38
48
|
|
|
39
49
|
const pathParams = useTypedSelector((state: any) => state.params.path);
|
|
40
50
|
const queryParams = useTypedSelector((state: any) => state.params.query);
|
|
@@ -116,11 +126,53 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
116
126
|
res.headers && dispatch(setHeaders(Object.fromEntries(res.headers)));
|
|
117
127
|
};
|
|
118
128
|
|
|
129
|
+
const getErrorMessage = (errorType: RequestErrorType): string => {
|
|
130
|
+
switch (errorType) {
|
|
131
|
+
case "timeout":
|
|
132
|
+
return translate({
|
|
133
|
+
id: OPENAPI_REQUEST.ERROR_TIMEOUT,
|
|
134
|
+
message:
|
|
135
|
+
"The request timed out waiting for the server to respond. Please try again. If the issue persists, try using a different client (e.g., curl) with a longer timeout.",
|
|
136
|
+
});
|
|
137
|
+
case "network":
|
|
138
|
+
return translate({
|
|
139
|
+
id: OPENAPI_REQUEST.ERROR_NETWORK,
|
|
140
|
+
message:
|
|
141
|
+
"Unable to reach the server. Please check your network connection and verify the server URL is correct. If the server is running, this may be a CORS issue.",
|
|
142
|
+
});
|
|
143
|
+
case "cors":
|
|
144
|
+
return translate({
|
|
145
|
+
id: OPENAPI_REQUEST.ERROR_CORS,
|
|
146
|
+
message:
|
|
147
|
+
"The request was blocked, possibly due to CORS restrictions. Ensure the server allows requests from this origin, or try using a proxy.",
|
|
148
|
+
});
|
|
149
|
+
case "unknown":
|
|
150
|
+
default:
|
|
151
|
+
return translate({
|
|
152
|
+
id: OPENAPI_REQUEST.ERROR_UNKNOWN,
|
|
153
|
+
message:
|
|
154
|
+
"An unexpected error occurred while making the request. Please try again.",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
119
159
|
const onSubmit = async (data) => {
|
|
120
|
-
dispatch(
|
|
160
|
+
dispatch(
|
|
161
|
+
setResponse(
|
|
162
|
+
translate({
|
|
163
|
+
id: OPENAPI_REQUEST.FETCHING_MESSAGE,
|
|
164
|
+
message: "Fetching...",
|
|
165
|
+
})
|
|
166
|
+
)
|
|
167
|
+
);
|
|
121
168
|
try {
|
|
122
169
|
await delay(1200);
|
|
123
|
-
const res = await makeRequest(
|
|
170
|
+
const res = await makeRequest(
|
|
171
|
+
postmanRequest,
|
|
172
|
+
proxy,
|
|
173
|
+
body,
|
|
174
|
+
requestTimeout
|
|
175
|
+
);
|
|
124
176
|
if (res.headers.get("content-type")?.includes("text/event-stream")) {
|
|
125
177
|
await handleEventStream(res);
|
|
126
178
|
} else {
|
|
@@ -128,7 +180,18 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
128
180
|
}
|
|
129
181
|
} catch (e) {
|
|
130
182
|
console.log(e);
|
|
131
|
-
|
|
183
|
+
|
|
184
|
+
let errorMessage: string;
|
|
185
|
+
if (e instanceof RequestError) {
|
|
186
|
+
errorMessage = getErrorMessage(e.type);
|
|
187
|
+
} else {
|
|
188
|
+
errorMessage = translate({
|
|
189
|
+
id: OPENAPI_REQUEST.CONNECTION_FAILED,
|
|
190
|
+
message: "Connection failed",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
dispatch(setResponse(errorMessage));
|
|
132
195
|
dispatch(clearCode());
|
|
133
196
|
dispatch(clearHeaders());
|
|
134
197
|
}
|
|
@@ -137,7 +200,7 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
137
200
|
const showServerOptions = serverOptions.length > 0;
|
|
138
201
|
const showAcceptOptions = acceptOptions.length > 1;
|
|
139
202
|
const showRequestBody = contentType !== undefined;
|
|
140
|
-
const showRequestButton = item.servers && !hideSendButton;
|
|
203
|
+
const showRequestButton = (item.servers || proxy) && !hideSendButton;
|
|
141
204
|
const showAuth = authSelected !== undefined;
|
|
142
205
|
const showParams = allParams.length > 0;
|
|
143
206
|
const requestBodyRequired = item.requestBody?.required;
|
|
@@ -147,7 +210,8 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
147
210
|
!showAuth &&
|
|
148
211
|
!showParams &&
|
|
149
212
|
!showRequestBody &&
|
|
150
|
-
!showServerOptions
|
|
213
|
+
!showServerOptions &&
|
|
214
|
+
!showRequestButton
|
|
151
215
|
) {
|
|
152
216
|
return null;
|
|
153
217
|
}
|
|
@@ -178,20 +242,31 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
178
242
|
onSubmit={methods.handleSubmit(onSubmit)}
|
|
179
243
|
>
|
|
180
244
|
<div className="openapi-explorer__request-header-container">
|
|
181
|
-
<span className="openapi-explorer__request-title">
|
|
245
|
+
<span className="openapi-explorer__request-title">
|
|
246
|
+
{translate({
|
|
247
|
+
id: OPENAPI_REQUEST.REQUEST_TITLE,
|
|
248
|
+
message: "Request",
|
|
249
|
+
})}
|
|
250
|
+
</span>
|
|
182
251
|
{allDetailsExpanded ? (
|
|
183
252
|
<span
|
|
184
253
|
className="openapi-explorer__expand-details-btn"
|
|
185
254
|
onClick={collapseAllDetails}
|
|
186
255
|
>
|
|
187
|
-
|
|
256
|
+
{translate({
|
|
257
|
+
id: OPENAPI_REQUEST.COLLAPSE_ALL,
|
|
258
|
+
message: "Collapse all",
|
|
259
|
+
})}
|
|
188
260
|
</span>
|
|
189
261
|
) : (
|
|
190
262
|
<span
|
|
191
263
|
className="openapi-explorer__expand-details-btn"
|
|
192
264
|
onClick={expandAllDetails}
|
|
193
265
|
>
|
|
194
|
-
|
|
266
|
+
{translate({
|
|
267
|
+
id: OPENAPI_REQUEST.EXPAND_ALL,
|
|
268
|
+
message: "Expand all",
|
|
269
|
+
})}
|
|
195
270
|
</span>
|
|
196
271
|
)}
|
|
197
272
|
</div>
|
|
@@ -208,7 +283,10 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
208
283
|
setExpandServer(!expandServer);
|
|
209
284
|
}}
|
|
210
285
|
>
|
|
211
|
-
|
|
286
|
+
{translate({
|
|
287
|
+
id: OPENAPI_REQUEST.BASE_URL_TITLE,
|
|
288
|
+
message: "Base URL",
|
|
289
|
+
})}
|
|
212
290
|
</summary>
|
|
213
291
|
<Server />
|
|
214
292
|
</details>
|
|
@@ -225,7 +303,7 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
225
303
|
setExpandAuth(!expandAuth);
|
|
226
304
|
}}
|
|
227
305
|
>
|
|
228
|
-
Auth
|
|
306
|
+
{translate({ id: OPENAPI_REQUEST.AUTH_TITLE, message: "Auth" })}
|
|
229
307
|
</summary>
|
|
230
308
|
<Authorization />
|
|
231
309
|
</details>
|
|
@@ -244,7 +322,10 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
244
322
|
setExpandParams(!expandParams);
|
|
245
323
|
}}
|
|
246
324
|
>
|
|
247
|
-
|
|
325
|
+
{translate({
|
|
326
|
+
id: OPENAPI_REQUEST.PARAMETERS_TITLE,
|
|
327
|
+
message: "Parameters",
|
|
328
|
+
})}
|
|
248
329
|
</summary>
|
|
249
330
|
<ParamOptions />
|
|
250
331
|
</details>
|
|
@@ -261,10 +342,14 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
261
342
|
setExpandBody(!expandBody);
|
|
262
343
|
}}
|
|
263
344
|
>
|
|
264
|
-
Body
|
|
345
|
+
{translate({ id: OPENAPI_REQUEST.BODY_TITLE, message: "Body" })}
|
|
265
346
|
{requestBodyRequired && (
|
|
266
347
|
<span className="openapi-schema__required">
|
|
267
|
-
|
|
348
|
+
|
|
349
|
+
{translate({
|
|
350
|
+
id: OPENAPI_REQUEST.REQUIRED_LABEL,
|
|
351
|
+
message: "required",
|
|
352
|
+
})}
|
|
268
353
|
</span>
|
|
269
354
|
)}
|
|
270
355
|
</summary>
|
|
@@ -290,14 +375,20 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
290
375
|
setExpandAccept(!expandAccept);
|
|
291
376
|
}}
|
|
292
377
|
>
|
|
293
|
-
|
|
378
|
+
{translate({
|
|
379
|
+
id: OPENAPI_REQUEST.ACCEPT_TITLE,
|
|
380
|
+
message: "Accept",
|
|
381
|
+
})}
|
|
294
382
|
</summary>
|
|
295
383
|
<Accept />
|
|
296
384
|
</details>
|
|
297
385
|
)}
|
|
298
386
|
{showRequestButton && item.method !== "event" && (
|
|
299
387
|
<button className="openapi-explorer__request-btn" type="submit">
|
|
300
|
-
|
|
388
|
+
{translate({
|
|
389
|
+
id: OPENAPI_REQUEST.SEND_BUTTON,
|
|
390
|
+
message: "Send API Request",
|
|
391
|
+
})}
|
|
301
392
|
</button>
|
|
302
393
|
)}
|
|
303
394
|
</div>
|
|
@@ -8,17 +8,83 @@
|
|
|
8
8
|
import { Body } from "@theme/ApiExplorer/Body/slice";
|
|
9
9
|
import * as sdk from "postman-collection";
|
|
10
10
|
|
|
11
|
+
// Custom error types for better error handling
|
|
12
|
+
export type RequestErrorType =
|
|
13
|
+
| "timeout"
|
|
14
|
+
| "network"
|
|
15
|
+
| "cors"
|
|
16
|
+
| "abort"
|
|
17
|
+
| "unknown";
|
|
18
|
+
|
|
19
|
+
export class RequestError extends Error {
|
|
20
|
+
type: RequestErrorType;
|
|
21
|
+
originalError?: Error;
|
|
22
|
+
|
|
23
|
+
constructor(type: RequestErrorType, message: string, originalError?: Error) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "RequestError";
|
|
26
|
+
this.type = type;
|
|
27
|
+
this.originalError = originalError;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
32
|
+
|
|
11
33
|
function fetchWithtimeout(
|
|
12
34
|
url: string,
|
|
13
35
|
options: RequestInit,
|
|
14
|
-
timeout =
|
|
15
|
-
):
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
timeout = DEFAULT_REQUEST_TIMEOUT
|
|
37
|
+
): Promise<Response> {
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
40
|
+
|
|
41
|
+
return fetch(url, {
|
|
42
|
+
...options,
|
|
43
|
+
signal: controller.signal,
|
|
44
|
+
})
|
|
45
|
+
.then((response) => {
|
|
46
|
+
clearTimeout(timeoutId);
|
|
47
|
+
return response;
|
|
48
|
+
})
|
|
49
|
+
.catch((error) => {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
|
|
52
|
+
// Check if it was an abort due to timeout
|
|
53
|
+
if (error.name === "AbortError") {
|
|
54
|
+
throw new RequestError(
|
|
55
|
+
"timeout",
|
|
56
|
+
"The request timed out waiting for the server to respond. Please try again. If the issue persists, try using a different client (e.g., curl) with a longer timeout.",
|
|
57
|
+
error
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for network errors (offline, DNS failure, etc.)
|
|
62
|
+
if (error instanceof TypeError && error.message === "Failed to fetch") {
|
|
63
|
+
// This could be CORS, network failure, or the server being unreachable
|
|
64
|
+
throw new RequestError(
|
|
65
|
+
"network",
|
|
66
|
+
"Unable to reach the server. Please check your network connection and verify the server URL is correct. If the server is running, this may be a CORS issue.",
|
|
67
|
+
error
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle other TypeErrors that might indicate CORS issues
|
|
72
|
+
if (error instanceof TypeError) {
|
|
73
|
+
throw new RequestError(
|
|
74
|
+
"cors",
|
|
75
|
+
"The request was blocked, possibly due to CORS restrictions. Ensure the server allows requests from this origin, or try using a proxy.",
|
|
76
|
+
error
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generic error fallback
|
|
81
|
+
throw new RequestError(
|
|
82
|
+
"unknown",
|
|
83
|
+
error.message ||
|
|
84
|
+
"An unexpected error occurred while making the request.",
|
|
85
|
+
error
|
|
86
|
+
);
|
|
87
|
+
});
|
|
22
88
|
}
|
|
23
89
|
|
|
24
90
|
async function loadImage(content: Blob): Promise<string | ArrayBuffer | null> {
|
|
@@ -47,7 +113,8 @@ async function loadImage(content: Blob): Promise<string | ArrayBuffer | null> {
|
|
|
47
113
|
async function makeRequest(
|
|
48
114
|
request: sdk.Request,
|
|
49
115
|
proxy: string | undefined,
|
|
50
|
-
_body: Body
|
|
116
|
+
_body: Body,
|
|
117
|
+
timeout: number = DEFAULT_REQUEST_TIMEOUT
|
|
51
118
|
) {
|
|
52
119
|
const headers = request.toJSON().header;
|
|
53
120
|
|
|
@@ -194,7 +261,8 @@ async function makeRequest(
|
|
|
194
261
|
finalUrl = normalizedProxy + request.url.toString();
|
|
195
262
|
}
|
|
196
263
|
|
|
197
|
-
|
|
264
|
+
try {
|
|
265
|
+
const response = await fetchWithtimeout(finalUrl, requestOptions, timeout);
|
|
198
266
|
const contentType = response.headers.get("content-type");
|
|
199
267
|
let fileExtension = "";
|
|
200
268
|
|
|
@@ -224,32 +292,45 @@ async function makeRequest(
|
|
|
224
292
|
}
|
|
225
293
|
|
|
226
294
|
if (fileExtension) {
|
|
227
|
-
|
|
228
|
-
|
|
295
|
+
const blob = await response.blob();
|
|
296
|
+
const url = window.URL.createObjectURL(blob);
|
|
229
297
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
298
|
+
const link = document.createElement("a");
|
|
299
|
+
link.href = url;
|
|
300
|
+
// Now the file name includes the extension
|
|
301
|
+
link.setAttribute("download", `file${fileExtension}`);
|
|
234
302
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
303
|
+
// These two lines are necessary to make the link click in Firefox
|
|
304
|
+
link.style.display = "none";
|
|
305
|
+
document.body.appendChild(link);
|
|
238
306
|
|
|
239
|
-
|
|
307
|
+
link.click();
|
|
240
308
|
|
|
241
|
-
|
|
242
|
-
|
|
309
|
+
// After link is clicked, it's safe to remove it.
|
|
310
|
+
setTimeout(() => document.body.removeChild(link), 0);
|
|
243
311
|
|
|
244
|
-
|
|
245
|
-
});
|
|
312
|
+
return response;
|
|
246
313
|
} else {
|
|
247
314
|
return response;
|
|
248
315
|
}
|
|
249
316
|
}
|
|
250
317
|
|
|
251
318
|
return response;
|
|
252
|
-
})
|
|
319
|
+
} catch (error) {
|
|
320
|
+
// Re-throw RequestError instances as-is
|
|
321
|
+
if (error instanceof RequestError) {
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Wrap unexpected errors
|
|
326
|
+
throw new RequestError(
|
|
327
|
+
"unknown",
|
|
328
|
+
error instanceof Error
|
|
329
|
+
? error.message
|
|
330
|
+
: "An unexpected error occurred while processing the response.",
|
|
331
|
+
error instanceof Error ? error : undefined
|
|
332
|
+
);
|
|
333
|
+
}
|
|
253
334
|
}
|
|
254
335
|
|
|
255
336
|
export default makeRequest;
|
|
@@ -9,12 +9,16 @@ import React from "react";
|
|
|
9
9
|
|
|
10
10
|
import { useDoc } from "@docusaurus/plugin-content-docs/client";
|
|
11
11
|
import { usePrismTheme } from "@docusaurus/theme-common";
|
|
12
|
+
import { translate } from "@docusaurus/Translate";
|
|
13
|
+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
12
14
|
import ApiCodeBlock from "@theme/ApiExplorer/ApiCodeBlock";
|
|
13
15
|
import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
|
|
14
16
|
import SchemaTabs from "@theme/SchemaTabs";
|
|
15
17
|
import TabItem from "@theme/TabItem";
|
|
18
|
+
import { OPENAPI_RESPONSE } from "@theme/translationIds";
|
|
16
19
|
import clsx from "clsx";
|
|
17
20
|
import { ApiItem } from "docusaurus-plugin-openapi-docs/src/types";
|
|
21
|
+
import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types";
|
|
18
22
|
|
|
19
23
|
import { clearResponse, clearCode, clearHeaders } from "./slice";
|
|
20
24
|
|
|
@@ -40,7 +44,11 @@ function formatXml(xml: string) {
|
|
|
40
44
|
|
|
41
45
|
function Response({ item }: { item: ApiItem }) {
|
|
42
46
|
const metadata = useDoc();
|
|
47
|
+
const { siteConfig } = useDocusaurusContext();
|
|
48
|
+
const themeConfig = siteConfig.themeConfig as ThemeConfig;
|
|
43
49
|
const hideSendButton = metadata.frontMatter.hide_send_button;
|
|
50
|
+
const proxy =
|
|
51
|
+
metadata.frontMatter.proxy ?? themeConfig.api?.proxy;
|
|
44
52
|
const prismTheme = usePrismTheme();
|
|
45
53
|
const code = useTypedSelector((state: any) => state.response.code);
|
|
46
54
|
const headers = useTypedSelector((state: any) => state.response.headers);
|
|
@@ -55,7 +63,7 @@ function Response({ item }: { item: ApiItem }) {
|
|
|
55
63
|
? "openapi-response__dot--success"
|
|
56
64
|
: "openapi-response__dot--info");
|
|
57
65
|
|
|
58
|
-
if (!item.servers || hideSendButton) {
|
|
66
|
+
if ((!item.servers && !proxy) || hideSendButton) {
|
|
59
67
|
return null;
|
|
60
68
|
}
|
|
61
69
|
|
|
@@ -74,7 +82,9 @@ function Response({ item }: { item: ApiItem }) {
|
|
|
74
82
|
return (
|
|
75
83
|
<div className="openapi-explorer__response-container">
|
|
76
84
|
<div className="openapi-explorer__response-title-container">
|
|
77
|
-
<span className="openapi-explorer__response-title">
|
|
85
|
+
<span className="openapi-explorer__response-title">
|
|
86
|
+
{translate({ id: OPENAPI_RESPONSE.TITLE, message: "Response" })}
|
|
87
|
+
</span>
|
|
78
88
|
<span
|
|
79
89
|
className="openapi-explorer__response-clear-btn"
|
|
80
90
|
onClick={() => {
|
|
@@ -83,7 +93,7 @@ function Response({ item }: { item: ApiItem }) {
|
|
|
83
93
|
dispatch(clearHeaders());
|
|
84
94
|
}}
|
|
85
95
|
>
|
|
86
|
-
Clear
|
|
96
|
+
{translate({ id: OPENAPI_RESPONSE.CLEAR, message: "Clear" })}
|
|
87
97
|
</span>
|
|
88
98
|
</div>
|
|
89
99
|
<div
|
|
@@ -117,14 +127,23 @@ function Response({ item }: { item: ApiItem }) {
|
|
|
117
127
|
>
|
|
118
128
|
{prettyResponse || (
|
|
119
129
|
<p className="openapi-explorer__response-placeholder-message">
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
{translate({
|
|
131
|
+
id: OPENAPI_RESPONSE.PLACEHOLDER,
|
|
132
|
+
message:
|
|
133
|
+
"Click the <code>Send API Request</code> button above and see the response here!",
|
|
134
|
+
})}
|
|
122
135
|
</p>
|
|
123
136
|
)}
|
|
124
137
|
</ApiCodeBlock>
|
|
125
138
|
</TabItem>
|
|
126
139
|
{/* @ts-ignore */}
|
|
127
|
-
<TabItem
|
|
140
|
+
<TabItem
|
|
141
|
+
label={translate({
|
|
142
|
+
id: OPENAPI_RESPONSE.HEADERS_TAB,
|
|
143
|
+
message: "Headers",
|
|
144
|
+
})}
|
|
145
|
+
value="headers"
|
|
146
|
+
>
|
|
128
147
|
{/* @ts-ignore */}
|
|
129
148
|
<ApiCodeBlock
|
|
130
149
|
className="openapi-explorer__code-block openapi-response__status-headers"
|
|
@@ -145,8 +164,11 @@ function Response({ item }: { item: ApiItem }) {
|
|
|
145
164
|
</div>
|
|
146
165
|
) : (
|
|
147
166
|
<p className="openapi-explorer__response-placeholder-message">
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
{translate({
|
|
168
|
+
id: OPENAPI_RESPONSE.PLACEHOLDER,
|
|
169
|
+
message:
|
|
170
|
+
"Click the <code>Send API Request</code> button above and see the response here!",
|
|
171
|
+
})}
|
|
150
172
|
</p>
|
|
151
173
|
)}
|
|
152
174
|
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Copyright (c) Palo Alto Networks
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
default as schemaSelectionReducer,
|
|
10
|
+
setSchemaSelection,
|
|
11
|
+
clearSchemaSelections,
|
|
12
|
+
} from "./slice";
|
|
13
|
+
export type { SchemaSelectionState } from "./slice";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Copyright (c) Palo Alto Networks
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
9
|
+
|
|
10
|
+
export interface SchemaSelectionState {
|
|
11
|
+
/**
|
|
12
|
+
* Maps schema path (e.g., "requestBody", "requestBody.anyOf.0.layer3")
|
|
13
|
+
* to the selected anyOf/oneOf option index
|
|
14
|
+
*/
|
|
15
|
+
selections: { [schemaPath: string]: number };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const initialState: SchemaSelectionState = {
|
|
19
|
+
selections: {},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const slice = createSlice({
|
|
23
|
+
name: "schemaSelection",
|
|
24
|
+
initialState,
|
|
25
|
+
reducers: {
|
|
26
|
+
/**
|
|
27
|
+
* Set the selected index for a specific schema path
|
|
28
|
+
*/
|
|
29
|
+
setSchemaSelection: (
|
|
30
|
+
state,
|
|
31
|
+
action: PayloadAction<{ path: string; index: number }>
|
|
32
|
+
) => {
|
|
33
|
+
state.selections[action.payload.path] = action.payload.index;
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Clear all schema selections (useful when navigating to a new API endpoint)
|
|
37
|
+
*/
|
|
38
|
+
clearSchemaSelections: (state) => {
|
|
39
|
+
state.selections = {};
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const { setSchemaSelection, clearSchemaSelections } = slice.actions;
|
|
45
|
+
|
|
46
|
+
export default slice.reducer;
|