fumadocs-openapi 4.3.0 → 4.4.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/dist/index.d.ts CHANGED
@@ -51,13 +51,20 @@ interface Renderer {
51
51
 
52
52
  declare const defaultRenderer: Renderer;
53
53
 
54
+ /**
55
+ * Sample info of endpoint
56
+ */
54
57
  interface Endpoint {
55
58
  /**
56
- * URL, including path and query parameters
59
+ * Request URL, including path and query parameters
57
60
  */
58
61
  url: string;
59
62
  method: string;
60
- body?: unknown;
63
+ body?: {
64
+ schema: OpenAPIV3.SchemaObject;
65
+ mediaType: string;
66
+ sample: unknown;
67
+ };
61
68
  responses: Record<string, Response>;
62
69
  parameters: Parameter[];
63
70
  }
@@ -68,6 +75,7 @@ interface Parameter {
68
75
  name: string;
69
76
  in: string;
70
77
  schema: OpenAPIV3.SchemaObject;
78
+ sample: unknown;
71
79
  }
72
80
 
73
81
  interface CodeSample {
package/dist/index.js CHANGED
@@ -143,6 +143,9 @@ function idToTitle(id) {
143
143
  return result.join("");
144
144
  }
145
145
 
146
+ // src/endpoint.ts
147
+ import { sample as sample2 } from "openapi-sampler";
148
+
146
149
  // src/utils/schema.ts
147
150
  function noRef(v) {
148
151
  return v;
@@ -173,18 +176,41 @@ function createEndpoint(path, method, baseUrl) {
173
176
  const params = [];
174
177
  const responses = {};
175
178
  for (const param of method.parameters) {
176
- const schema = noRef(
177
- param.schema ?? getPreferredMedia(param.content ?? {})?.schema
178
- );
179
- if (!schema) continue;
180
- params.push({
181
- name: param.name,
182
- in: param.in,
183
- schema
184
- });
179
+ if (param.schema) {
180
+ params.push({
181
+ name: param.name,
182
+ in: param.in,
183
+ schema: noRef(param.schema),
184
+ sample: param.example ?? sample2(param.schema)
185
+ });
186
+ } else if (param.content) {
187
+ const key = getPreferredType(param.content);
188
+ const content = key ? param.content[key] : void 0;
189
+ if (!key || !content?.schema)
190
+ throw new Error(
191
+ `Cannot find parameter schema for ${param.name} in ${path} ${method.method}`
192
+ );
193
+ params.push({
194
+ name: param.name,
195
+ in: param.in,
196
+ schema: noRef(content.schema),
197
+ sample: content.example ?? param.example ?? sample2(content.schema)
198
+ });
199
+ }
200
+ }
201
+ let bodyOutput;
202
+ if (method.requestBody) {
203
+ const body = noRef(method.requestBody).content;
204
+ const type = getPreferredType(body);
205
+ const schema = type ? noRef(body[type].schema) : void 0;
206
+ if (!type || !schema)
207
+ throw new Error(`Cannot find body schema for ${path} ${method.method}`);
208
+ bodyOutput = {
209
+ schema,
210
+ mediaType: type,
211
+ sample: body[type].example ?? generateInput(method.method, schema)
212
+ };
185
213
  }
186
- const body = noRef(method.requestBody)?.content ?? {};
187
- const bodySchema = noRef(getPreferredMedia(body)?.schema);
188
214
  for (const [code, value] of Object.entries(method.responses)) {
189
215
  const mediaTypes = noRef(value).content ?? {};
190
216
  const responseSchema = noRef(getPreferredMedia(mediaTypes)?.schema);
@@ -209,7 +235,7 @@ function createEndpoint(path, method, baseUrl) {
209
235
  pathWithParameters = `${pathWithParameters}?${queryParams.toString()}`;
210
236
  return {
211
237
  url: new URL(`${baseUrl}${pathWithParameters}`).toString(),
212
- body: bodySchema ? generateInput(method.method, bodySchema) : void 0,
238
+ body: bodyOutput,
213
239
  responses,
214
240
  method: method.method,
215
241
  parameters: params
@@ -233,15 +259,13 @@ function getSampleRequest(endpoint) {
233
259
  s.push(`curl -X ${endpoint.method} "${endpoint.url}"`);
234
260
  for (const param of endpoint.parameters) {
235
261
  if (param.in === "header") {
236
- const value = generateInput(endpoint.method, param.schema);
237
- const header = `${param.name}: ${toSampleInput(value)}`;
262
+ const header = `${param.name}: ${toSampleInput(param.sample)}`;
238
263
  s.push(`-H "${header}"`);
239
264
  }
240
- if (param.in === "formData") {
241
- console.log("Request example for form data is not supported");
242
- }
243
265
  }
244
- if (endpoint.body) s.push(`-d '${toSampleInput(endpoint.body)}'`);
266
+ if (endpoint.body?.mediaType === "multipart/form-data")
267
+ console.warn("Curl sample with form data body isn't supported.");
268
+ if (endpoint.body) s.push(`-d '${toSampleInput(endpoint.body.sample)}'`);
245
269
  return s.join(" \\\n ");
246
270
  }
247
271
 
@@ -250,24 +274,25 @@ function getSampleRequest2(endpoint) {
250
274
  const s = [];
251
275
  const options = /* @__PURE__ */ new Map();
252
276
  const headers = {};
253
- const formData = {};
254
277
  for (const param of endpoint.parameters) {
255
278
  if (param.in === "header") {
256
279
  headers[param.name] = generateInput(endpoint.method, param.schema);
257
280
  }
258
- if (param.in === "formData") {
259
- formData[param.name] = generateInput(endpoint.method, param.schema);
260
- }
261
281
  }
262
282
  options.set("method", JSON.stringify(endpoint.method));
263
283
  if (Object.keys(headers).length > 0) {
264
284
  options.set("headers", JSON.stringify(headers, void 0, 2));
265
285
  }
266
- if (Object.keys(formData).length > 0) {
286
+ if (endpoint.body?.mediaType === "multipart/form-data" && typeof endpoint.body.sample === "object" && endpoint.body.sample) {
267
287
  s.push(`const formData = new FormData();`);
268
- for (const [key, value] of Object.entries(formData))
269
- s.push(`formData.set(${key}, ${JSON.stringify(value)}`);
288
+ for (const [key, value] of Object.entries(endpoint.body.sample))
289
+ s.push(`formData.set(${key}, ${JSON.stringify(value)})`);
270
290
  options.set("body", "formData");
291
+ } else if (endpoint.body) {
292
+ options.set(
293
+ "body",
294
+ `JSON.stringify(${JSON.stringify(endpoint.body.sample, null, 2).split("\n").map((v, i) => i > 0 ? ` ${v}` : v).join("\n")})`
295
+ );
271
296
  }
272
297
  const optionsStr = Array.from(options.entries()).map(([k, v]) => ` ${k}: ${v}`).join(",\n");
273
298
  s.push(`fetch(${JSON.stringify(endpoint.url)}, {
@@ -15,7 +15,7 @@ import {
15
15
  useRef,
16
16
  useState as useState3
17
17
  } from "react";
18
- import { Controller as Controller2, useForm } from "react-hook-form";
18
+ import { Controller as Controller2, useForm, useWatch } from "react-hook-form";
19
19
  import useSWRImmutable from "swr/immutable";
20
20
  import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
21
21
  import { cn as cn5, buttonVariants as buttonVariants2 } from "fumadocs-ui/components/api";
@@ -889,16 +889,12 @@ function APIPlayground({
889
889
  input ? [baseUrl, route, method, input, bodyType] : null,
890
890
  async () => {
891
891
  if (!input) return;
892
- let pathname = route;
893
- Object.keys(input.path).forEach((key) => {
894
- const paramValue = input.path[key];
895
- if (typeof paramValue === "string")
896
- pathname = pathname.replace(`{${key}}`, paramValue);
897
- });
898
- const url = new URL(`${baseUrl ?? window.location.origin}${pathname}`);
892
+ const url = new URL(
893
+ `${baseUrl ?? window.location.origin}${createPathnameFromInput(route, input.path)}`
894
+ );
899
895
  Object.keys(input.query).forEach((key) => {
900
896
  const paramValue = input.query[key];
901
- if (typeof paramValue === "string")
897
+ if (typeof paramValue === "string" && paramValue.length > 0)
902
898
  url.searchParams.append(key, paramValue);
903
899
  });
904
900
  const headers = new Headers({
@@ -909,7 +905,8 @@ function APIPlayground({
909
905
  }
910
906
  Object.keys(input.header).forEach((key) => {
911
907
  const paramValue = input.header[key];
912
- if (typeof paramValue === "string") headers.append(key, paramValue);
908
+ if (typeof paramValue === "string" && paramValue.length > 0)
909
+ headers.append(key, paramValue);
913
910
  });
914
911
  const bodyValue = body && input.body && Object.keys(input.body).length > 0 ? createBodyFromValue(
915
912
  bodyType,
@@ -969,7 +966,7 @@ function APIPlayground({
969
966
  onSubmit,
970
967
  children: [
971
968
  /* @__PURE__ */ jsxs3("div", { className: "flex flex-row gap-2", children: [
972
- /* @__PURE__ */ jsx6("code", { className: "flex-1 overflow-auto rounded-lg border bg-fd-secondary px-3 py-1.5 text-sm", children: route }),
969
+ /* @__PURE__ */ jsx6(RouteDisplay, { route }),
973
970
  /* @__PURE__ */ jsx6(
974
971
  "button",
975
972
  {
@@ -1025,6 +1022,25 @@ function APIPlayground({
1025
1022
  }
1026
1023
  ) });
1027
1024
  }
1025
+ function createPathnameFromInput(route, input) {
1026
+ let pathname = route;
1027
+ Object.keys(input).forEach((key) => {
1028
+ const paramValue = input[key];
1029
+ if (typeof paramValue === "string" && paramValue.length > 0)
1030
+ pathname = pathname.replace(`{${key}}`, paramValue);
1031
+ });
1032
+ return pathname;
1033
+ }
1034
+ function RouteDisplay({ route }) {
1035
+ const pathInput = useWatch({
1036
+ name: "path"
1037
+ });
1038
+ const pathname = useMemo(
1039
+ () => createPathnameFromInput(route, pathInput),
1040
+ [route, pathInput]
1041
+ );
1042
+ return /* @__PURE__ */ jsx6("code", { className: "flex-1 overflow-auto text-nowrap rounded-lg border bg-fd-secondary px-3 py-1.5 text-sm", children: pathname });
1043
+ }
1028
1044
  function ResultDisplay({
1029
1045
  data
1030
1046
  }) {
package/dist/ui/index.js CHANGED
@@ -71,12 +71,12 @@ function APIInfo({
71
71
  method = "GET",
72
72
  ...props
73
73
  }) {
74
- return /* @__PURE__ */ jsxs("div", { className: cn("min-w-0 flex-1 prose-no-margin", className), ...props, children: [
74
+ return /* @__PURE__ */ jsxs("div", { className: cn("min-w-0 flex-1", className), ...props, children: [
75
75
  /* @__PURE__ */ jsxs(
76
76
  "div",
77
77
  {
78
78
  className: cn(
79
- "sticky top-24 z-[2] flex flex-row items-center gap-2 rounded-lg border bg-fd-card p-3 md:top-10"
79
+ "sticky top-24 z-[2] mb-4 flex flex-row items-center gap-2 rounded-lg border bg-fd-card p-3 md:top-10"
80
80
  ),
81
81
  children: [
82
82
  /* @__PURE__ */ jsx(
@@ -94,7 +94,7 @@ function APIInfo({
94
94
  ]
95
95
  }
96
96
  ),
97
- children
97
+ /* @__PURE__ */ jsx("div", { className: "prose-no-margin", children })
98
98
  ] });
99
99
  }
100
100
  function Property({
@@ -176,7 +176,7 @@ function CopyRouteButton({
176
176
 
177
177
  // src/ui/index.ts
178
178
  var APIPlayground = dynamic(
179
- () => import("../playground-5XDMCOXX.js").then((mod) => mod.APIPlayground)
179
+ () => import("../playground-GIGTWHCL.js").then((mod) => mod.APIPlayground)
180
180
  );
181
181
  var Responses = Tabs;
182
182
  var Response = Tab;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -56,8 +56,8 @@
56
56
  "react-hook-form": "^7.52.1",
57
57
  "shiki": "^1.11.1",
58
58
  "swr": "^2.2.5",
59
- "fumadocs-core": "13.0.4",
60
- "fumadocs-ui": "13.0.4"
59
+ "fumadocs-core": "13.0.5",
60
+ "fumadocs-ui": "13.0.5"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/js-yaml": "^4.0.9",