docusaurus-theme-openapi-docs 0.0.0-1160 → 0.0.0-1162

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 (37) hide show
  1. package/lib/theme/ApiExplorer/Body/FormBodyItem/index.d.ts +2 -1
  2. package/lib/theme/ApiExplorer/Body/FormBodyItem/index.js +47 -0
  3. package/lib/theme/ApiExplorer/Body/index.js +4 -0
  4. package/lib/theme/ApiExplorer/CodeSnippets/index.d.ts +2 -1
  5. package/lib/theme/ApiExplorer/CodeSnippets/index.js +4 -0
  6. package/lib/theme/ApiExplorer/ContentType/index.js +5 -1
  7. package/lib/theme/ApiExplorer/EncodingSelection/slice.d.ts +17 -0
  8. package/lib/theme/ApiExplorer/EncodingSelection/slice.js +29 -0
  9. package/lib/theme/ApiExplorer/EncodingSelection/useResolvedEncoding.d.ts +12 -0
  10. package/lib/theme/ApiExplorer/EncodingSelection/useResolvedEncoding.js +39 -0
  11. package/lib/theme/ApiExplorer/Request/index.js +7 -1
  12. package/lib/theme/ApiExplorer/Request/makeRequest.d.ts +3 -1
  13. package/lib/theme/ApiExplorer/Request/makeRequest.js +17 -3
  14. package/lib/theme/ApiExplorer/buildPostmanRequest.d.ts +4 -1
  15. package/lib/theme/ApiExplorer/buildPostmanRequest.js +46 -5
  16. package/lib/theme/ApiExplorer/index.js +1 -0
  17. package/lib/theme/ApiExplorer/persistenceMiddleware.d.ts +2 -0
  18. package/lib/theme/ApiItem/hooks.d.ts +1 -0
  19. package/lib/theme/ApiItem/index.js +1 -0
  20. package/lib/theme/ApiItem/store.d.ts +6 -0
  21. package/lib/theme/ApiItem/store.js +11 -7
  22. package/lib/theme/RequestSchema/index.js +172 -109
  23. package/package.json +3 -3
  24. package/src/theme/ApiExplorer/Body/FormBodyItem/index.tsx +55 -10
  25. package/src/theme/ApiExplorer/Body/index.tsx +5 -0
  26. package/src/theme/ApiExplorer/CodeSnippets/index.tsx +9 -1
  27. package/src/theme/ApiExplorer/ContentType/index.tsx +5 -3
  28. package/src/theme/ApiExplorer/EncodingSelection/slice.ts +31 -0
  29. package/src/theme/ApiExplorer/EncodingSelection/useResolvedEncoding.ts +43 -0
  30. package/src/theme/ApiExplorer/Request/index.tsx +6 -1
  31. package/src/theme/ApiExplorer/Request/makeRequest.ts +17 -3
  32. package/src/theme/ApiExplorer/buildPostmanRequest.ts +52 -5
  33. package/src/theme/ApiExplorer/index.tsx +1 -0
  34. package/src/theme/ApiItem/index.tsx +1 -0
  35. package/src/theme/ApiItem/store.ts +2 -0
  36. package/src/theme/RequestSchema/index.tsx +141 -83
  37. package/tsconfig.tsbuildinfo +1 -1
@@ -17,7 +17,9 @@ const Translate_1 = require("@docusaurus/Translate");
17
17
  const Details_1 = __importDefault(require("@theme/Details"));
18
18
  const Markdown_1 = __importDefault(require("@theme/Markdown"));
19
19
  const MimeTabs_1 = __importDefault(require("@theme/MimeTabs")); // Assume these components exist
20
+ const ResponseExamples_1 = require("@theme/ResponseExamples");
20
21
  const Schema_1 = __importDefault(require("@theme/Schema"));
22
+ const SchemaTabs_1 = __importDefault(require("@theme/SchemaTabs"));
21
23
  const SkeletonLoader_1 = __importDefault(require("@theme/SkeletonLoader"));
22
24
  const TabItem_1 = __importDefault(require("@theme/TabItem"));
23
25
  const translationIds_1 = require("@theme/translationIds");
@@ -36,7 +38,10 @@ const RequestSchemaComponent = ({ title, body, style }) => {
36
38
  MimeTabs_1.default,
37
39
  { className: "openapi-tabs__mime", schemaType: "request", lazy: true },
38
40
  mimeTypes.map((mimeType) => {
39
- const firstBody = body.content[mimeType].schema;
41
+ const mediaTypeObject = body.content[mimeType];
42
+ const firstBody = mediaTypeObject?.schema;
43
+ const requestExamples = mediaTypeObject?.examples;
44
+ const requestExample = mediaTypeObject?.example;
40
45
  if (
41
46
  firstBody === undefined ||
42
47
  (firstBody.properties &&
@@ -50,68 +55,96 @@ const RequestSchemaComponent = ({ title, body, style }) => {
50
55
  TabItem_1.default,
51
56
  { key: mimeType, label: mimeType, value: mimeType },
52
57
  react_1.default.createElement(
53
- "div",
54
- { style: { marginTop: "1rem" } },
58
+ SchemaTabs_1.default,
59
+ { className: "openapi-tabs__schema" },
55
60
  react_1.default.createElement(
56
- Details_1.default,
57
- {
58
- className: "openapi-markdown__details mime",
59
- "data-collapsed": false,
60
- open: true,
61
- style: style,
62
- summary: react_1.default.createElement(
63
- react_1.default.Fragment,
64
- null,
65
- react_1.default.createElement(
66
- "summary",
67
- null,
68
- react_1.default.createElement(
69
- "h3",
70
- {
71
- className:
72
- "openapi-markdown__details-summary-header-body",
73
- },
74
- (0, Translate_1.translate)({
75
- id: translationIds_1.OPENAPI_REQUEST.BODY_TITLE,
76
- message: title,
77
- }),
78
- body.required === true &&
61
+ TabItem_1.default,
62
+ { key: title, label: title, value: title },
63
+ react_1.default.createElement(
64
+ "div",
65
+ { style: { marginTop: "1rem" } },
66
+ react_1.default.createElement(
67
+ Details_1.default,
68
+ {
69
+ className: "openapi-markdown__details mime",
70
+ "data-collapsed": false,
71
+ open: true,
72
+ style: style,
73
+ summary: react_1.default.createElement(
74
+ react_1.default.Fragment,
75
+ null,
76
+ react_1.default.createElement(
77
+ "summary",
78
+ null,
79
79
  react_1.default.createElement(
80
- "span",
81
- { className: "openapi-schema__required" },
80
+ "h3",
81
+ {
82
+ className:
83
+ "openapi-markdown__details-summary-header-body",
84
+ },
82
85
  (0, Translate_1.translate)({
83
- id: translationIds_1.OPENAPI_SCHEMA_ITEM.REQUIRED,
84
- message: "required",
85
- })
86
+ id: translationIds_1.OPENAPI_REQUEST.BODY_TITLE,
87
+ message: title,
88
+ }),
89
+ body.required === true &&
90
+ react_1.default.createElement(
91
+ "span",
92
+ { className: "openapi-schema__required" },
93
+ (0, Translate_1.translate)({
94
+ id: translationIds_1.OPENAPI_SCHEMA_ITEM
95
+ .REQUIRED,
96
+ message: "required",
97
+ })
98
+ )
86
99
  )
87
- )
88
- )
89
- ),
90
- },
91
- react_1.default.createElement(
92
- "div",
93
- { style: { textAlign: "left", marginLeft: "1rem" } },
94
- body.description &&
100
+ )
101
+ ),
102
+ },
95
103
  react_1.default.createElement(
96
104
  "div",
97
- { style: { marginTop: "1rem", marginBottom: "1rem" } },
98
- react_1.default.createElement(
99
- Markdown_1.default,
100
- null,
101
- body.description
102
- )
105
+ { style: { textAlign: "left", marginLeft: "1rem" } },
106
+ body.description &&
107
+ react_1.default.createElement(
108
+ "div",
109
+ {
110
+ style: { marginTop: "1rem", marginBottom: "1rem" },
111
+ },
112
+ react_1.default.createElement(
113
+ Markdown_1.default,
114
+ null,
115
+ body.description
116
+ )
117
+ )
118
+ ),
119
+ react_1.default.createElement(
120
+ "ul",
121
+ { style: { marginLeft: "1rem" } },
122
+ react_1.default.createElement(Schema_1.default, {
123
+ schema: firstBody,
124
+ schemaType: "request",
125
+ schemaPath: "requestBody",
126
+ })
103
127
  )
104
- ),
105
- react_1.default.createElement(
106
- "ul",
107
- { style: { marginLeft: "1rem" } },
108
- react_1.default.createElement(Schema_1.default, {
109
- schema: firstBody,
110
- schemaType: "request",
111
- schemaPath: "requestBody",
112
- })
128
+ )
113
129
  )
114
- )
130
+ ),
131
+ firstBody &&
132
+ !requestExample &&
133
+ !requestExamples &&
134
+ (0, ResponseExamples_1.ExampleFromSchema)({
135
+ schema: firstBody,
136
+ mimeType: mimeType,
137
+ }),
138
+ requestExamples &&
139
+ (0, ResponseExamples_1.ResponseExamples)({
140
+ responseExamples: requestExamples,
141
+ mimeType,
142
+ }),
143
+ requestExample &&
144
+ (0, ResponseExamples_1.ResponseExample)({
145
+ responseExample: requestExample,
146
+ mimeType,
147
+ })
115
148
  )
116
149
  )
117
150
  );
@@ -119,8 +152,10 @@ const RequestSchemaComponent = ({ title, body, style }) => {
119
152
  );
120
153
  }
121
154
  const randomFirstKey = mimeTypes[0];
122
- const firstBody =
123
- body.content[randomFirstKey].schema ?? body.content[randomFirstKey];
155
+ const mediaTypeObject = body.content[randomFirstKey];
156
+ const firstBody = mediaTypeObject?.schema ?? body.content[randomFirstKey];
157
+ const requestExamples = mediaTypeObject?.examples;
158
+ const requestExample = mediaTypeObject?.example;
124
159
  if (firstBody === undefined) {
125
160
  return null;
126
161
  }
@@ -131,67 +166,95 @@ const RequestSchemaComponent = ({ title, body, style }) => {
131
166
  TabItem_1.default,
132
167
  { label: randomFirstKey, value: `${randomFirstKey}-schema` },
133
168
  react_1.default.createElement(
134
- Details_1.default,
135
- {
136
- className: "openapi-markdown__details mime",
137
- "data-collapsed": false,
138
- open: true,
139
- style: style,
140
- summary: react_1.default.createElement(
141
- react_1.default.Fragment,
142
- null,
143
- react_1.default.createElement(
144
- "summary",
145
- null,
146
- react_1.default.createElement(
147
- "h3",
148
- { className: "openapi-markdown__details-summary-header-body" },
149
- (0, Translate_1.translate)({
150
- id: translationIds_1.OPENAPI_REQUEST.BODY_TITLE,
151
- message: title,
152
- }),
153
- firstBody.type === "array" &&
154
- react_1.default.createElement(
155
- "span",
156
- { style: { opacity: "0.6" } },
157
- " array"
158
- ),
159
- body.required &&
169
+ SchemaTabs_1.default,
170
+ { className: "openapi-tabs__schema" },
171
+ react_1.default.createElement(
172
+ TabItem_1.default,
173
+ { key: title, label: title, value: title },
174
+ react_1.default.createElement(
175
+ Details_1.default,
176
+ {
177
+ className: "openapi-markdown__details mime",
178
+ "data-collapsed": false,
179
+ open: true,
180
+ style: style,
181
+ summary: react_1.default.createElement(
182
+ react_1.default.Fragment,
183
+ null,
184
+ react_1.default.createElement(
185
+ "summary",
186
+ null,
160
187
  react_1.default.createElement(
161
- "strong",
162
- { className: "openapi-schema__required" },
188
+ "h3",
189
+ {
190
+ className:
191
+ "openapi-markdown__details-summary-header-body",
192
+ },
163
193
  (0, Translate_1.translate)({
164
- id: translationIds_1.OPENAPI_SCHEMA_ITEM.REQUIRED,
165
- message: "required",
166
- })
194
+ id: translationIds_1.OPENAPI_REQUEST.BODY_TITLE,
195
+ message: title,
196
+ }),
197
+ firstBody.type === "array" &&
198
+ react_1.default.createElement(
199
+ "span",
200
+ { style: { opacity: "0.6" } },
201
+ " array"
202
+ ),
203
+ body.required &&
204
+ react_1.default.createElement(
205
+ "strong",
206
+ { className: "openapi-schema__required" },
207
+ (0, Translate_1.translate)({
208
+ id: translationIds_1.OPENAPI_SCHEMA_ITEM.REQUIRED,
209
+ message: "required",
210
+ })
211
+ )
167
212
  )
168
- )
169
- )
170
- ),
171
- },
172
- react_1.default.createElement(
173
- "div",
174
- { style: { textAlign: "left", marginLeft: "1rem" } },
175
- body.description &&
213
+ )
214
+ ),
215
+ },
176
216
  react_1.default.createElement(
177
217
  "div",
178
- { style: { marginTop: "1rem", marginBottom: "1rem" } },
179
- react_1.default.createElement(
180
- Markdown_1.default,
181
- null,
182
- body.description
183
- )
218
+ { style: { textAlign: "left", marginLeft: "1rem" } },
219
+ body.description &&
220
+ react_1.default.createElement(
221
+ "div",
222
+ { style: { marginTop: "1rem", marginBottom: "1rem" } },
223
+ react_1.default.createElement(
224
+ Markdown_1.default,
225
+ null,
226
+ body.description
227
+ )
228
+ )
229
+ ),
230
+ react_1.default.createElement(
231
+ "ul",
232
+ { style: { marginLeft: "1rem" } },
233
+ react_1.default.createElement(Schema_1.default, {
234
+ schema: firstBody,
235
+ schemaType: "request",
236
+ schemaPath: "requestBody",
237
+ })
184
238
  )
239
+ )
185
240
  ),
186
- react_1.default.createElement(
187
- "ul",
188
- { style: { marginLeft: "1rem" } },
189
- react_1.default.createElement(Schema_1.default, {
241
+ firstBody &&
242
+ !requestExample &&
243
+ !requestExamples &&
244
+ (0, ResponseExamples_1.ExampleFromSchema)({
190
245
  schema: firstBody,
191
- schemaType: "request",
192
- schemaPath: "requestBody",
246
+ mimeType: randomFirstKey,
247
+ }),
248
+ requestExamples &&
249
+ (0, ResponseExamples_1.ResponseExamples)({
250
+ responseExamples: requestExamples,
251
+ mimeType: randomFirstKey,
252
+ }),
253
+ requestExample &&
254
+ (0, ResponseExamples_1.ResponseExample)({
255
+ responseExample: requestExample,
256
+ mimeType: randomFirstKey,
193
257
  })
194
- )
195
258
  )
196
259
  )
197
260
  );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "docusaurus-theme-openapi-docs",
3
3
  "description": "OpenAPI theme for Docusaurus.",
4
- "version": "0.0.0-1160",
4
+ "version": "0.0.0-1162",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "openapi",
@@ -38,7 +38,7 @@
38
38
  "@types/postman-collection": "^3.5.11",
39
39
  "@types/react-modal": "^3.16.3",
40
40
  "concurrently": "^9.2.0",
41
- "docusaurus-plugin-openapi-docs": "0.0.0-1160",
41
+ "docusaurus-plugin-openapi-docs": "0.0.0-1162",
42
42
  "docusaurus-plugin-sass": "^0.2.6",
43
43
  "eslint-plugin-prettier": "^5.5.1"
44
44
  },
@@ -82,5 +82,5 @@
82
82
  "engines": {
83
83
  "node": ">=14"
84
84
  },
85
- "gitHead": "9590f36ccddb212cb31943e14654c4a9a6884ec5"
85
+ "gitHead": "91084c914a86e2522437018b04dd0a811e463451"
86
86
  }
@@ -17,6 +17,7 @@ import type { SchemaObject } from "docusaurus-plugin-openapi-docs/src/openapi/ty
17
17
 
18
18
  import FileArrayFormBodyItem from "../FileArrayFormBodyItem";
19
19
  import { clearFormBodyKey, setFileFormBody, setStringFormBody } from "../slice";
20
+ import { setFieldEncoding } from "../../EncodingSelection/slice";
20
21
 
21
22
  interface FormBodyItemProps {
22
23
  schemaObject: SchemaObject;
@@ -25,6 +26,7 @@ interface FormBodyItemProps {
25
26
  label?: string;
26
27
  required?: boolean;
27
28
  exampleValue?: SchemaObject["example"];
29
+ fieldEncoding?: string;
28
30
  }
29
31
 
30
32
  export default function FormBodyItem({
@@ -34,8 +36,37 @@ export default function FormBodyItem({
34
36
  label,
35
37
  required,
36
38
  exampleValue,
39
+ fieldEncoding,
37
40
  }: FormBodyItemProps): React.JSX.Element {
38
41
  const dispatch = useTypedDispatch();
42
+
43
+ // Parse comma-separated encoding contentType into selectable options
44
+ const encodingOptions = fieldEncoding
45
+ ? fieldEncoding
46
+ .split(",")
47
+ .map((t) => t.trim())
48
+ .filter(Boolean)
49
+ : [];
50
+ const hasMultipleEncodings = encodingOptions.length > 1;
51
+
52
+ // Initialize with the first declared content type
53
+ const [selectedEncoding, setSelectedEncoding] = useState<string>(
54
+ encodingOptions[0] ?? ""
55
+ );
56
+
57
+ // Seed Redux with the first declared encoding on mount so the code snippet
58
+ // reflects a content type immediately, even before the user interacts.
59
+ // The empty dep array is intentional: `fieldEncoding` comes from a static
60
+ // spec value that never changes for the lifetime of this component instance,
61
+ // and re-seeding on every render would fight user selections.
62
+ useEffect(() => {
63
+ if (encodingOptions[0]) {
64
+ dispatch(
65
+ setFieldEncoding({ field: id, contentType: encodingOptions[0] })
66
+ );
67
+ }
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, []);
39
70
  const [value, setValue] = useState(() => {
40
71
  let initialValue = exampleValue ?? "";
41
72
 
@@ -71,6 +102,20 @@ export default function FormBodyItem({
71
102
  return (
72
103
  <>
73
104
  {label && <FormLabel label={label} required={required} />}
105
+ {hasMultipleEncodings && (
106
+ <div style={{ marginTop: "0.5rem" }}>
107
+ <FormSelect
108
+ label="Content-Type"
109
+ options={encodingOptions}
110
+ value={selectedEncoding}
111
+ onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
112
+ const ct = e.target.value;
113
+ setSelectedEncoding(ct);
114
+ dispatch(setFieldEncoding({ field: id, contentType: ct }));
115
+ }}
116
+ />
117
+ </div>
118
+ )}
74
119
  <FormFileUpload
75
120
  placeholder={schemaObject.description || id}
76
121
  onChange={(file: any) => {
@@ -95,16 +140,16 @@ export default function FormBodyItem({
95
140
 
96
141
  if (schemaObject.type === "object") {
97
142
  return (
98
- <>
99
- {label && <FormLabel label={label} required={required} />}
100
- <LiveApp
101
- action={(code: string) =>
102
- dispatch(setStringFormBody({ key: id, value: code }))
103
- }
104
- >
105
- {value}
106
- </LiveApp>
107
- </>
143
+ <>
144
+ {label && <FormLabel label={label} required={required} />}
145
+ <LiveApp
146
+ action={(code: string) =>
147
+ dispatch(setStringFormBody({ key: id, value: code }))
148
+ }
149
+ >
150
+ {value}
151
+ </LiveApp>
152
+ </>
108
153
  );
109
154
  }
110
155
 
@@ -93,6 +93,8 @@ function Body({
93
93
  const rawSchema = requestBodyMetadata?.content?.[contentType]?.schema;
94
94
  const example = requestBodyMetadata?.content?.[contentType]?.example;
95
95
  const examples = requestBodyMetadata?.content?.[contentType]?.examples;
96
+ const encoding: Record<string, { contentType?: string }> | undefined =
97
+ requestBodyMetadata?.content?.[contentType]?.encoding;
96
98
 
97
99
  // Resolve the schema based on user's anyOf/oneOf tab selections
98
100
  const schema = useMemo(() => {
@@ -339,6 +341,7 @@ function Body({
339
341
  Array.isArray(schema.required) &&
340
342
  schema.required.includes(key)
341
343
  }
344
+ fieldEncoding={encoding?.[key]?.contentType}
342
345
  ></FormBodyItem>
343
346
  </FormItem>
344
347
  );
@@ -361,6 +364,7 @@ function Body({
361
364
  Array.isArray(schema.required) &&
362
365
  schema.required.includes(schemaKey)
363
366
  }
367
+ fieldEncoding={encoding?.[schemaKey]?.contentType}
364
368
  ></FormBodyItem>
365
369
  </FormItem>
366
370
  );
@@ -387,6 +391,7 @@ function Body({
387
391
  Array.isArray(schema.required) &&
388
392
  schema.required.includes(schemaKey)
389
393
  }
394
+ fieldEncoding={encoding?.[schemaKey]?.contentType}
390
395
  ></FormBodyItem>
391
396
  </FormItem>
392
397
  );
@@ -11,6 +11,7 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
11
11
  import ApiCodeBlock from "@theme/ApiExplorer/ApiCodeBlock";
12
12
  import buildPostmanRequest from "@theme/ApiExplorer/buildPostmanRequest";
13
13
  import CodeTabs from "@theme/ApiExplorer/CodeTabs";
14
+ import { useResolvedEncoding } from "@theme/ApiExplorer/EncodingSelection/useResolvedEncoding";
14
15
  import { useTypedSelector } from "@theme/ApiItem/hooks";
15
16
  import cloneDeep from "lodash/cloneDeep";
16
17
  import codegen from "postman-code-generators";
@@ -30,6 +31,7 @@ export interface Props {
30
31
  postman: sdk.Request;
31
32
  codeSamples: CodeSample[];
32
33
  maskCredentials?: boolean;
34
+ requestBody?: import("docusaurus-plugin-openapi-docs/src/openapi/types").RequestBodyObject;
33
35
  }
34
36
 
35
37
  function CodeTab({ children, hidden, className }: any): React.JSX.Element {
@@ -44,6 +46,7 @@ function CodeSnippets({
44
46
  postman,
45
47
  codeSamples,
46
48
  maskCredentials: propMaskCredentials,
49
+ requestBody,
47
50
  }: Props) {
48
51
  const { siteConfig } = useDocusaurusContext();
49
52
 
@@ -76,7 +79,9 @@ function CodeSnippets({
76
79
  const authOptions =
77
80
  clonedAuth?.options?.[key] ??
78
81
  clonedAuth?.options?.[comboAuthId];
79
- placeholder = authOptions?.find((opt: any) => opt.key === key)?.name;
82
+ placeholder = authOptions?.find(
83
+ (opt: any) => opt.key === key
84
+ )?.name;
80
85
  obj[key] = cleanCredentials(obj[key]);
81
86
  } else {
82
87
  obj[key] = `<${placeholder ?? key}>`;
@@ -93,6 +98,8 @@ function CodeSnippets({
93
98
  })()
94
99
  : auth;
95
100
 
101
+ const encoding = useResolvedEncoding(requestBody);
102
+
96
103
  // Create a Postman request object using cleanedAuth or original auth
97
104
  const cleanedPostmanRequest = buildPostmanRequest(postman, {
98
105
  queryParams,
@@ -104,6 +111,7 @@ function CodeSnippets({
104
111
  body,
105
112
  server,
106
113
  auth: cleanedAuth,
114
+ encoding,
107
115
  });
108
116
 
109
117
  // User-defined languages array
@@ -12,6 +12,7 @@ import FormSelect from "@theme/ApiExplorer/FormSelect";
12
12
  import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
13
13
 
14
14
  import { setContentType } from "./slice";
15
+ import { clearEncodingSelection } from "@theme/ApiExplorer/EncodingSelection/slice";
15
16
 
16
17
  function ContentType() {
17
18
  const value = useTypedSelector((state: any) => state.contentType.value);
@@ -28,9 +29,10 @@ function ContentType() {
28
29
  label="Content-Type"
29
30
  value={value}
30
31
  options={options}
31
- onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
32
- dispatch(setContentType(e.target.value))
33
- }
32
+ onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
33
+ dispatch(setContentType(e.target.value));
34
+ dispatch(clearEncodingSelection());
35
+ }}
34
36
  />
35
37
  </FormItem>
36
38
  );
@@ -0,0 +1,31 @@
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
+ // Maps form field name → user-selected content type
11
+ export type State = Record<string, string>;
12
+
13
+ const initialState: State = {};
14
+
15
+ export const slice = createSlice({
16
+ name: "encodingSelection",
17
+ initialState,
18
+ reducers: {
19
+ setFieldEncoding: (
20
+ state,
21
+ action: PayloadAction<{ field: string; contentType: string }>
22
+ ) => {
23
+ state[action.payload.field] = action.payload.contentType;
24
+ },
25
+ clearEncodingSelection: () => initialState,
26
+ },
27
+ });
28
+
29
+ export const { setFieldEncoding, clearEncodingSelection } = slice.actions;
30
+
31
+ export default slice.reducer;
@@ -0,0 +1,43 @@
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 { useTypedSelector } from "@theme/ApiItem/hooks";
9
+ import type { RequestBodyObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
10
+
11
+ /**
12
+ * Merges the spec-declared `encoding` for the active content type with any
13
+ * per-field content-type selections the user has made in the UI. User picks
14
+ * take precedence over the spec default.
15
+ *
16
+ * Returns `undefined` when no encoding is declared for the current content
17
+ * type so callers can skip the encoding path entirely.
18
+ */
19
+ export function useResolvedEncoding(
20
+ requestBody: RequestBodyObject | undefined
21
+ ): Record<string, { contentType?: string }> | undefined {
22
+ const contentType = useTypedSelector((state: any) => state.contentType.value);
23
+ const encodingSelection = useTypedSelector(
24
+ (state: any) => state.encodingSelection
25
+ );
26
+
27
+ const specEncoding: Record<string, { contentType?: string }> =
28
+ requestBody?.content?.[contentType]?.encoding ?? {};
29
+
30
+ if (!Object.keys(specEncoding).length) {
31
+ return undefined;
32
+ }
33
+
34
+ return Object.fromEntries(
35
+ Object.entries(specEncoding).map(([field, enc]) => [
36
+ field,
37
+ {
38
+ ...enc,
39
+ contentType: encodingSelection[field] ?? enc.contentType,
40
+ },
41
+ ])
42
+ );
43
+ }
@@ -25,6 +25,7 @@ import {
25
25
  clearHeaders,
26
26
  } from "@theme/ApiExplorer/Response/slice";
27
27
  import Server from "@theme/ApiExplorer/Server";
28
+ import { useResolvedEncoding } from "@theme/ApiExplorer/EncodingSelection/useResolvedEncoding";
28
29
  import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
29
30
  import { OPENAPI_REQUEST } from "@theme/translationIds";
30
31
  import type { ParameterObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
@@ -76,6 +77,8 @@ function Request({ item }: { item: ApiItem }) {
76
77
  ...headerParams,
77
78
  ];
78
79
 
80
+ const encoding = useResolvedEncoding(item.requestBody);
81
+
79
82
  const postmanRequest = buildPostmanRequest(postman, {
80
83
  queryParams,
81
84
  pathParams,
@@ -86,6 +89,7 @@ function Request({ item }: { item: ApiItem }) {
86
89
  body,
87
90
  server,
88
91
  auth,
92
+ encoding,
89
93
  });
90
94
 
91
95
  const delay = (ms: number) =>
@@ -175,7 +179,8 @@ function Request({ item }: { item: ApiItem }) {
175
179
  proxy,
176
180
  body,
177
181
  requestTimeout,
178
- requestCredentials
182
+ requestCredentials,
183
+ encoding
179
184
  );
180
185
  if (res.headers.get("content-type")?.includes("text/event-stream")) {
181
186
  await handleEventStream(res);