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.
Files changed (133) hide show
  1. package/lib/markdown/schema.js +14 -1
  2. package/lib/theme/ApiExplorer/Accept/slice.d.ts +5 -2
  3. package/lib/theme/ApiExplorer/Authorization/index.js +50 -9
  4. package/lib/theme/ApiExplorer/Authorization/slice.d.ts +145 -3
  5. package/lib/theme/ApiExplorer/Authorization/slice.js +3 -1
  6. package/lib/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.d.ts +7 -0
  7. package/lib/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.js +126 -0
  8. package/lib/theme/ApiExplorer/Body/FormBodyItem/index.d.ts +9 -0
  9. package/lib/theme/ApiExplorer/Body/FormBodyItem/index.js +110 -0
  10. package/lib/theme/ApiExplorer/Body/index.js +322 -193
  11. package/lib/theme/ApiExplorer/Body/resolveSchemaWithSelections.d.ts +13 -0
  12. package/lib/theme/ApiExplorer/Body/resolveSchemaWithSelections.js +133 -0
  13. package/lib/theme/ApiExplorer/Body/slice.d.ts +1056 -11
  14. package/lib/theme/ApiExplorer/Body/slice.js +22 -2
  15. package/lib/theme/ApiExplorer/CodeSnippets/index.d.ts +2 -1
  16. package/lib/theme/ApiExplorer/CodeSnippets/index.js +37 -26
  17. package/lib/theme/ApiExplorer/CodeTabs/_CodeTabs.scss +5 -1
  18. package/lib/theme/ApiExplorer/CodeTabs/index.d.ts +3 -3
  19. package/lib/theme/ApiExplorer/CodeTabs/index.js +2 -2
  20. package/lib/theme/ApiExplorer/ContentType/slice.d.ts +5 -2
  21. package/lib/theme/ApiExplorer/FormFileUpload/index.js +6 -1
  22. package/lib/theme/ApiExplorer/FormItem/index.js +6 -1
  23. package/lib/theme/ApiExplorer/FormTextInput/index.d.ts +2 -0
  24. package/lib/theme/ApiExplorer/FormTextInput/index.js +8 -1
  25. package/lib/theme/ApiExplorer/LiveEditor/index.js +11 -4
  26. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.js +15 -5
  27. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js +11 -3
  28. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.js +12 -4
  29. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.js +11 -2
  30. package/lib/theme/ApiExplorer/ParamOptions/index.js +11 -1
  31. package/lib/theme/ApiExplorer/ParamOptions/slice.d.ts +0 -4
  32. package/lib/theme/ApiExplorer/ParamOptions/slice.js +4 -4
  33. package/lib/theme/ApiExplorer/Request/index.js +110 -17
  34. package/lib/theme/ApiExplorer/Request/makeRequest.d.ts +7 -1
  35. package/lib/theme/ApiExplorer/Request/makeRequest.js +94 -24
  36. package/lib/theme/ApiExplorer/Response/index.js +34 -14
  37. package/lib/theme/ApiExplorer/Response/slice.d.ts +31 -7
  38. package/lib/theme/ApiExplorer/SchemaSelection/index.d.ts +2 -0
  39. package/lib/theme/ApiExplorer/SchemaSelection/index.js +36 -0
  40. package/lib/theme/ApiExplorer/SchemaSelection/slice.d.ts +37 -0
  41. package/lib/theme/ApiExplorer/SchemaSelection/slice.js +39 -0
  42. package/lib/theme/ApiExplorer/SecuritySchemes/index.js +208 -69
  43. package/lib/theme/ApiExplorer/Server/index.js +16 -2
  44. package/lib/theme/ApiExplorer/Server/slice.d.ts +49 -3
  45. package/lib/theme/ApiExplorer/buildPostmanRequest.js +46 -57
  46. package/lib/theme/ApiExplorer/index.js +4 -0
  47. package/lib/theme/ApiExplorer/persistenceMiddleware.d.ts +21 -0
  48. package/lib/theme/ApiExplorer/{persistanceMiddleware.js → persistenceMiddleware.js} +16 -9
  49. package/lib/theme/ApiExplorer/storage-utils.d.ts +2 -2
  50. package/lib/theme/ApiExplorer/storage-utils.js +3 -3
  51. package/lib/theme/ApiItem/Layout/index.d.ts +1 -1
  52. package/lib/theme/ApiItem/hooks.d.ts +10 -9
  53. package/lib/theme/ApiItem/index.js +13 -8
  54. package/lib/theme/ApiItem/store.d.ts +61 -43
  55. package/lib/theme/ApiItem/store.js +6 -2
  56. package/lib/theme/ApiTabs/index.js +6 -1
  57. package/lib/theme/Example/_Example.scss +11 -0
  58. package/lib/theme/Example/index.d.ts +24 -0
  59. package/lib/theme/Example/index.js +170 -0
  60. package/lib/theme/ParamsDetails/index.js +9 -1
  61. package/lib/theme/ParamsItem/index.d.ts +1 -1
  62. package/lib/theme/ParamsItem/index.js +43 -74
  63. package/lib/theme/RequestSchema/index.js +68 -48
  64. package/lib/theme/ResponseExamples/index.js +23 -3
  65. package/lib/theme/ResponseSchema/index.js +97 -82
  66. package/lib/theme/Schema/index.d.ts +6 -0
  67. package/lib/theme/Schema/index.js +240 -31
  68. package/lib/theme/SchemaItem/index.js +64 -36
  69. package/lib/theme/SchemaTabs/index.d.ts +8 -1
  70. package/lib/theme/SchemaTabs/index.js +14 -2
  71. package/lib/theme/StatusCodes/index.d.ts +1 -1
  72. package/lib/theme/StatusCodes/index.js +11 -2
  73. package/lib/theme/styles.scss +15 -0
  74. package/lib/theme/translationIds.d.ts +90 -0
  75. package/lib/theme/translationIds.js +114 -0
  76. package/package.json +28 -28
  77. package/src/markdown/schema.ts +17 -1
  78. package/src/theme/ApiExplorer/Authorization/index.tsx +51 -10
  79. package/src/theme/ApiExplorer/Authorization/slice.ts +1 -1
  80. package/src/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.tsx +77 -0
  81. package/src/theme/ApiExplorer/Body/FormBodyItem/index.tsx +120 -0
  82. package/src/theme/ApiExplorer/Body/index.tsx +262 -198
  83. package/{lib/types.js → src/theme/ApiExplorer/Body/json2xml.d.ts} +2 -2
  84. package/src/theme/ApiExplorer/Body/resolveSchemaWithSelections.ts +155 -0
  85. package/src/theme/ApiExplorer/Body/slice.ts +40 -1
  86. package/src/theme/ApiExplorer/CodeSnippets/index.tsx +43 -29
  87. package/src/theme/ApiExplorer/CodeTabs/_CodeTabs.scss +5 -1
  88. package/src/theme/ApiExplorer/CodeTabs/index.tsx +6 -5
  89. package/src/theme/ApiExplorer/ContentType/index.tsx +1 -1
  90. package/src/theme/ApiExplorer/FormFileUpload/index.tsx +6 -1
  91. package/src/theme/ApiExplorer/FormItem/index.tsx +8 -1
  92. package/src/theme/ApiExplorer/FormTextInput/index.tsx +10 -1
  93. package/src/theme/ApiExplorer/LiveEditor/index.tsx +11 -4
  94. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.tsx +16 -6
  95. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.tsx +12 -4
  96. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx +12 -4
  97. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx +12 -3
  98. package/src/theme/ApiExplorer/ParamOptions/index.tsx +10 -2
  99. package/src/theme/ApiExplorer/ParamOptions/slice.ts +1 -1
  100. package/src/theme/ApiExplorer/Request/index.tsx +108 -17
  101. package/src/theme/ApiExplorer/Request/makeRequest.ts +106 -25
  102. package/src/theme/ApiExplorer/Response/index.tsx +30 -8
  103. package/src/theme/ApiExplorer/SchemaSelection/index.ts +13 -0
  104. package/src/theme/ApiExplorer/SchemaSelection/slice.ts +46 -0
  105. package/src/theme/ApiExplorer/SecuritySchemes/index.tsx +157 -69
  106. package/src/theme/ApiExplorer/Server/index.tsx +12 -4
  107. package/src/theme/ApiExplorer/buildPostmanRequest.ts +47 -63
  108. package/src/theme/ApiExplorer/index.tsx +5 -0
  109. package/src/theme/ApiExplorer/{persistanceMiddleware.ts → persistenceMiddleware.ts} +23 -13
  110. package/src/theme/ApiExplorer/storage-utils.ts +4 -4
  111. package/src/theme/ApiItem/Layout/index.tsx +1 -1
  112. package/src/theme/ApiItem/index.tsx +13 -7
  113. package/src/theme/ApiItem/store.ts +2 -0
  114. package/src/theme/ApiTabs/index.tsx +6 -1
  115. package/src/theme/Example/_Example.scss +11 -0
  116. package/src/theme/Example/index.tsx +168 -0
  117. package/src/theme/Markdown/index.d.ts +8 -0
  118. package/src/theme/ParamsDetails/index.tsx +10 -1
  119. package/src/theme/ParamsItem/index.tsx +38 -54
  120. package/src/theme/RequestSchema/index.tsx +60 -35
  121. package/src/theme/ResponseExamples/index.tsx +23 -3
  122. package/src/theme/ResponseSchema/index.tsx +73 -61
  123. package/src/theme/Schema/index.tsx +307 -55
  124. package/src/theme/SchemaItem/index.tsx +51 -33
  125. package/src/theme/SchemaTabs/index.tsx +19 -5
  126. package/src/theme/StatusCodes/index.tsx +13 -3
  127. package/src/theme/styles.scss +15 -0
  128. package/src/theme/translationIds.ts +111 -0
  129. package/src/theme-openapi.d.ts +7 -275
  130. package/src/{types.ts → types.d.ts} +9 -1
  131. package/tsconfig.tsbuildinfo +1 -1
  132. package/lib/theme/ApiExplorer/persistanceMiddleware.d.ts +0 -3
  133. 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={{ required: param.required ? "This field is required" : false }}
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, name } }) => (
47
+ render={({ field: { onChange } }) => (
39
48
  <FormSelect
40
49
  options={["---", ...(options as string[])]}
41
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
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
- ? "Hide optional parameters"
123
- : "Show optional parameters"}
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
@@ -19,7 +19,7 @@ export interface State {
19
19
 
20
20
  const initialState: State = {} as any;
21
21
 
22
- export const slice = createSlice({
22
+ const slice = createSlice({
23
23
  name: "params",
24
24
  initialState,
25
25
  reducers: {
@@ -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 } = metadata.frontMatter;
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(setResponse("Fetching..."));
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(postmanRequest, proxy, body);
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
- dispatch(setResponse("Connection failed"));
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">Request </span>
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
- Collapse all
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
- Expand all
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
- Base URL
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
- Parameters
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
- &nbsp;required
348
+ &nbsp;
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
- Accept
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
- Send API Request
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 = 5000
15
- ): any {
16
- return Promise.race([
17
- fetch(url, options),
18
- new Promise((_, reject) =>
19
- setTimeout(() => reject(new Error("Request timed out")), timeout)
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
- return fetchWithtimeout(finalUrl, requestOptions).then((response: any) => {
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
- return response.blob().then((blob: any) => {
228
- const url = window.URL.createObjectURL(blob);
295
+ const blob = await response.blob();
296
+ const url = window.URL.createObjectURL(blob);
229
297
 
230
- const link = document.createElement("a");
231
- link.href = url;
232
- // Now the file name includes the extension
233
- link.setAttribute("download", `file${fileExtension}`);
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
- // These two lines are necessary to make the link click in Firefox
236
- link.style.display = "none";
237
- document.body.appendChild(link);
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
- link.click();
307
+ link.click();
240
308
 
241
- // After link is clicked, it's safe to remove it.
242
- setTimeout(() => document.body.removeChild(link), 0);
309
+ // After link is clicked, it's safe to remove it.
310
+ setTimeout(() => document.body.removeChild(link), 0);
243
311
 
244
- return response;
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">Response</span>
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
- Click the <code>Send API Request</code> button above and see
121
- the response here!
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 label="Headers" value="headers">
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
- Click the <code>Send API Request</code> button above and see the
149
- response here!
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;