fumadocs-openapi 3.0.0 → 3.1.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 +9 -1
- package/dist/index.js +159 -41
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ interface PropertyProps {
|
|
|
19
19
|
interface ObjectCollapsibleProps {
|
|
20
20
|
name: string;
|
|
21
21
|
}
|
|
22
|
+
interface RequestProps {
|
|
23
|
+
language: string;
|
|
24
|
+
name: string;
|
|
25
|
+
code: string;
|
|
26
|
+
}
|
|
22
27
|
interface Renderer {
|
|
23
28
|
Root: (child: string[]) => string;
|
|
24
29
|
API: (child: string[]) => string;
|
|
@@ -26,6 +31,8 @@ interface Renderer {
|
|
|
26
31
|
APIExample: (child: string[]) => string;
|
|
27
32
|
Responses: (props: ResponsesProps, child: string[]) => string;
|
|
28
33
|
Response: (props: ResponseProps, child: string[]) => string;
|
|
34
|
+
Requests: (items: string[], child: string[]) => string;
|
|
35
|
+
Request: (props: RequestProps) => string;
|
|
29
36
|
ResponseTypes: (child: string[]) => string;
|
|
30
37
|
ExampleResponse: (json: string) => string;
|
|
31
38
|
TypeScriptResponse: (code: string) => string;
|
|
@@ -97,9 +104,10 @@ interface MethodInformation extends OpenAPIV3.OperationObject {
|
|
|
97
104
|
}
|
|
98
105
|
interface RenderContext {
|
|
99
106
|
renderer: Renderer;
|
|
107
|
+
document: OpenAPIV3.Document;
|
|
100
108
|
baseUrl: string;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
declare function createElement(name: string, props: object, ...child: string[]): string;
|
|
104
112
|
|
|
105
|
-
export { type APIInfoProps, type Config, type GenerateOptions, type GenerateTagOutput, type MethodInformation, type ObjectCollapsibleProps, type PropertyProps, type RenderContext, type Renderer, type ResponseProps, type ResponsesProps, type RouteInformation, createElement, defaultRenderer, generate, generateFiles, generateTags };
|
|
113
|
+
export { type APIInfoProps, type Config, type GenerateOptions, type GenerateTagOutput, type MethodInformation, type ObjectCollapsibleProps, type PropertyProps, type RenderContext, type Renderer, type RequestProps, type ResponseProps, type ResponsesProps, type RouteInformation, createElement, defaultRenderer, generate, generateFiles, generateTags };
|
package/dist/index.js
CHANGED
|
@@ -89,7 +89,9 @@ var defaultRenderer = {
|
|
|
89
89
|
codeblock({ language: "ts" }, code)
|
|
90
90
|
),
|
|
91
91
|
Property: (props, child) => createElement("Property", props, ...child),
|
|
92
|
-
ObjectCollapsible: (props, child) => createElement("ObjectCollapsible", props, ...child)
|
|
92
|
+
ObjectCollapsible: (props, child) => createElement("ObjectCollapsible", props, ...child),
|
|
93
|
+
Requests: (items, child) => createElement("Requests", { items }, ...child),
|
|
94
|
+
Request: ({ language, code, name }) => createElement("Request", { value: name }, codeblock({ language }, code))
|
|
93
95
|
};
|
|
94
96
|
|
|
95
97
|
// src/render/page.ts
|
|
@@ -117,10 +119,7 @@ ${finalImports}
|
|
|
117
119
|
${Root(content)}`;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
// src/
|
|
121
|
-
import { sample } from "openapi-sampler";
|
|
122
|
-
|
|
123
|
-
// src/utils.ts
|
|
122
|
+
// src/utils/schema.ts
|
|
124
123
|
function noRef(v) {
|
|
125
124
|
return v;
|
|
126
125
|
}
|
|
@@ -129,11 +128,20 @@ function getPreferredMedia(body) {
|
|
|
129
128
|
if ("application/json" in body) return body["application/json"];
|
|
130
129
|
return Object.values(body)[0];
|
|
131
130
|
}
|
|
132
|
-
function
|
|
131
|
+
function toSampleInput(value) {
|
|
133
132
|
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
// src/
|
|
135
|
+
// src/utils/generate-input.ts
|
|
136
|
+
import { sample } from "openapi-sampler";
|
|
137
|
+
function generateInput(method, schema) {
|
|
138
|
+
return sample(schema, {
|
|
139
|
+
skipReadOnly: method !== "GET",
|
|
140
|
+
skipWriteOnly: method === "GET"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/endpoint.ts
|
|
137
145
|
function createEndpoint(path, method, baseUrl) {
|
|
138
146
|
const params = [];
|
|
139
147
|
const responses = {};
|
|
@@ -161,33 +169,28 @@ function createEndpoint(path, method, baseUrl) {
|
|
|
161
169
|
let pathWithParameters = path;
|
|
162
170
|
const queryParams = new URLSearchParams();
|
|
163
171
|
for (const param of params) {
|
|
164
|
-
const value =
|
|
165
|
-
if (param.in === "query")
|
|
172
|
+
const value = generateInput(method.method, param.schema);
|
|
173
|
+
if (param.in === "query")
|
|
174
|
+
queryParams.append(param.name, toSampleInput(value));
|
|
166
175
|
if (param.in === "path")
|
|
167
176
|
pathWithParameters = pathWithParameters.replace(
|
|
168
177
|
`{${param.name}}`,
|
|
169
|
-
|
|
178
|
+
toSampleInput(value)
|
|
170
179
|
);
|
|
171
180
|
}
|
|
172
181
|
return {
|
|
173
182
|
url: new URL(pathWithParameters, baseUrl).toString(),
|
|
174
|
-
body: bodySchema ?
|
|
183
|
+
body: bodySchema ? generateInput(method.method, bodySchema) : void 0,
|
|
175
184
|
responses,
|
|
176
185
|
method: method.method,
|
|
177
186
|
parameters: params
|
|
178
187
|
};
|
|
179
188
|
}
|
|
180
|
-
function generateSample(method, schema) {
|
|
181
|
-
return sample(schema, {
|
|
182
|
-
skipReadOnly: method !== "GET",
|
|
183
|
-
skipWriteOnly: method === "GET"
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
189
|
|
|
187
|
-
// src/
|
|
190
|
+
// src/utils/generate-response.ts
|
|
188
191
|
function getExampleResponse(endpoint, code) {
|
|
189
192
|
if (code in endpoint.responses) {
|
|
190
|
-
const value =
|
|
193
|
+
const value = generateInput(
|
|
191
194
|
endpoint.method,
|
|
192
195
|
endpoint.responses[code].schema
|
|
193
196
|
);
|
|
@@ -195,38 +198,66 @@ function getExampleResponse(endpoint, code) {
|
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
// src/
|
|
199
|
-
import { compile } from "json-schema-to-typescript";
|
|
200
|
-
async function getTypescript(endpoint, code) {
|
|
201
|
-
if (code in endpoint.responses) {
|
|
202
|
-
return compile(endpoint.responses[code].schema, "Response", {
|
|
203
|
-
bannerComment: "",
|
|
204
|
-
additionalProperties: false,
|
|
205
|
-
format: true,
|
|
206
|
-
enableConstEnums: false
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// src/samples/curl.ts
|
|
201
|
+
// src/requests/curl.ts
|
|
212
202
|
function getSampleRequest(endpoint) {
|
|
213
203
|
const s = [];
|
|
214
204
|
s.push(`curl -X ${endpoint.method} "${endpoint.url}"`);
|
|
215
205
|
for (const param of endpoint.parameters) {
|
|
216
206
|
if (param.in === "header") {
|
|
217
|
-
const value =
|
|
218
|
-
const header = `${param.name}: ${
|
|
207
|
+
const value = generateInput(endpoint.method, param.schema);
|
|
208
|
+
const header = `${param.name}: ${toSampleInput(value)}`;
|
|
219
209
|
s.push(`-H "${header}"`);
|
|
220
210
|
}
|
|
221
211
|
if (param.in === "formData") {
|
|
222
212
|
console.log("Request example for form data is not supported");
|
|
223
213
|
}
|
|
224
214
|
}
|
|
225
|
-
if (endpoint.body) s.push(`-d '${
|
|
215
|
+
if (endpoint.body) s.push(`-d '${toSampleInput(endpoint.body)}'`);
|
|
226
216
|
return s.join(" \\\n ");
|
|
227
217
|
}
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
|
|
219
|
+
// src/requests/javascript.ts
|
|
220
|
+
function getSampleRequest2(endpoint) {
|
|
221
|
+
const s = [];
|
|
222
|
+
const options = /* @__PURE__ */ new Map();
|
|
223
|
+
const headers = {};
|
|
224
|
+
const formData = {};
|
|
225
|
+
for (const param of endpoint.parameters) {
|
|
226
|
+
if (param.in === "header") {
|
|
227
|
+
headers[param.name] = generateInput(endpoint.method, param.schema);
|
|
228
|
+
}
|
|
229
|
+
if (param.in === "formData") {
|
|
230
|
+
formData[param.name] = generateInput(endpoint.method, param.schema);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
options.set("method", JSON.stringify(endpoint.method));
|
|
234
|
+
if (Object.keys(headers).length > 0) {
|
|
235
|
+
options.set("headers", JSON.stringify(headers, void 0, 2));
|
|
236
|
+
}
|
|
237
|
+
if (Object.keys(formData).length > 0) {
|
|
238
|
+
s.push(`const formData = new FormData();`);
|
|
239
|
+
for (const [key, value] of Object.entries(formData))
|
|
240
|
+
s.push(`formData.set(${key}, ${JSON.stringify(value)}`);
|
|
241
|
+
options.set("body", "formData");
|
|
242
|
+
}
|
|
243
|
+
const optionsStr = Array.from(options.entries()).map(([k, v]) => ` ${k}: ${v}`).join(",\n");
|
|
244
|
+
s.push(`fetch(${JSON.stringify(endpoint.url)}, {
|
|
245
|
+
${optionsStr}
|
|
246
|
+
});`);
|
|
247
|
+
return s.join("\n\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/utils/get-typescript-schema.ts
|
|
251
|
+
import { compile } from "json-schema-to-typescript";
|
|
252
|
+
async function getTypescriptSchema(endpoint, code) {
|
|
253
|
+
if (code in endpoint.responses) {
|
|
254
|
+
return compile(endpoint.responses[code].schema, "Response", {
|
|
255
|
+
bannerComment: "",
|
|
256
|
+
additionalProperties: false,
|
|
257
|
+
format: true,
|
|
258
|
+
enableConstEnums: false
|
|
259
|
+
});
|
|
260
|
+
}
|
|
230
261
|
}
|
|
231
262
|
|
|
232
263
|
// src/render/schema.ts
|
|
@@ -345,12 +376,17 @@ function getSchemaType(schema) {
|
|
|
345
376
|
|
|
346
377
|
// src/render/operation.ts
|
|
347
378
|
async function renderOperation(path, method, ctx) {
|
|
379
|
+
const body = noRef(method.requestBody);
|
|
380
|
+
const security = method.security ?? ctx.document.security;
|
|
348
381
|
const info = [];
|
|
349
382
|
const example = [];
|
|
350
383
|
const title = method.summary ?? method.operationId;
|
|
351
384
|
if (title) info.push(`## ${title}`);
|
|
352
385
|
if (method.description) info.push(p(method.description));
|
|
353
|
-
|
|
386
|
+
if (security) {
|
|
387
|
+
info.push("### Authorization");
|
|
388
|
+
info.push(getAuthSection(security, ctx));
|
|
389
|
+
}
|
|
354
390
|
if (body) {
|
|
355
391
|
const bodySchema = getPreferredMedia(body.content)?.schema;
|
|
356
392
|
if (!bodySchema) throw new Error();
|
|
@@ -403,7 +439,21 @@ async function renderOperation(path, method, ctx) {
|
|
|
403
439
|
}
|
|
404
440
|
info.push(getResponseTable(method));
|
|
405
441
|
example.push(
|
|
406
|
-
|
|
442
|
+
ctx.renderer.Requests(
|
|
443
|
+
["cURL", "JavaScript"],
|
|
444
|
+
[
|
|
445
|
+
ctx.renderer.Request({
|
|
446
|
+
name: "cURL",
|
|
447
|
+
code: getSampleRequest(endpoint),
|
|
448
|
+
language: "bash"
|
|
449
|
+
}),
|
|
450
|
+
ctx.renderer.Request({
|
|
451
|
+
name: "JavaScript",
|
|
452
|
+
code: getSampleRequest2(endpoint),
|
|
453
|
+
language: "js"
|
|
454
|
+
})
|
|
455
|
+
]
|
|
456
|
+
)
|
|
407
457
|
);
|
|
408
458
|
example.push(await getResponseTabs(endpoint, method, ctx));
|
|
409
459
|
return ctx.renderer.API([
|
|
@@ -411,6 +461,73 @@ async function renderOperation(path, method, ctx) {
|
|
|
411
461
|
ctx.renderer.APIExample(example)
|
|
412
462
|
]);
|
|
413
463
|
}
|
|
464
|
+
function getAuthSection(requirements, { document, renderer }) {
|
|
465
|
+
const info = [];
|
|
466
|
+
const schemas = document.components?.securitySchemes ?? {};
|
|
467
|
+
for (const requirement of requirements) {
|
|
468
|
+
if (info.length > 0) info.push(`---`);
|
|
469
|
+
for (const [name, scopes] of Object.entries(requirement)) {
|
|
470
|
+
if (!(name in schemas)) continue;
|
|
471
|
+
const schema = noRef(schemas[name]);
|
|
472
|
+
if (schema.type === "http") {
|
|
473
|
+
info.push(
|
|
474
|
+
renderer.Property(
|
|
475
|
+
{
|
|
476
|
+
name: "Authorization",
|
|
477
|
+
type: {
|
|
478
|
+
basic: "Basic <token>",
|
|
479
|
+
bearer: "Bearer <token>"
|
|
480
|
+
}[schema.scheme] ?? "<token>",
|
|
481
|
+
required: true
|
|
482
|
+
},
|
|
483
|
+
[p(schema.description), `In: \`header\``]
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
if (schema.type === "oauth2") {
|
|
488
|
+
info.push(
|
|
489
|
+
renderer.Property(
|
|
490
|
+
{
|
|
491
|
+
name: "Authorization",
|
|
492
|
+
type: "Bearer <token>",
|
|
493
|
+
required: true
|
|
494
|
+
},
|
|
495
|
+
[
|
|
496
|
+
p(schema.description),
|
|
497
|
+
`In: \`header\``,
|
|
498
|
+
`Scope: \`${scopes.length > 0 ? scopes.join(", ") : "none"}\``
|
|
499
|
+
]
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (schema.type === "apiKey") {
|
|
504
|
+
info.push(
|
|
505
|
+
renderer.Property(
|
|
506
|
+
{
|
|
507
|
+
name: schema.name,
|
|
508
|
+
type: "<token>",
|
|
509
|
+
required: true
|
|
510
|
+
},
|
|
511
|
+
[p(schema.description), `In: \`${schema.in}\``]
|
|
512
|
+
)
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (schema.type === "openIdConnect") {
|
|
516
|
+
info.push(
|
|
517
|
+
renderer.Property(
|
|
518
|
+
{
|
|
519
|
+
name: "OpenID Connect",
|
|
520
|
+
type: "<token>",
|
|
521
|
+
required: true
|
|
522
|
+
},
|
|
523
|
+
[p(schema.description)]
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return info.join("\n\n");
|
|
530
|
+
}
|
|
414
531
|
function getResponseTable(operation) {
|
|
415
532
|
const table = [];
|
|
416
533
|
table.push(`| Status code | Description |`);
|
|
@@ -425,7 +542,7 @@ async function getResponseTabs(endpoint, operation, { renderer }) {
|
|
|
425
542
|
const child = [];
|
|
426
543
|
for (const code of Object.keys(operation.responses)) {
|
|
427
544
|
const example = getExampleResponse(endpoint, code);
|
|
428
|
-
const ts = await
|
|
545
|
+
const ts = await getTypescriptSchema(endpoint, code);
|
|
429
546
|
const description = code in endpoint.responses ? endpoint.responses[code].schema.description : void 0;
|
|
430
547
|
if (example && ts) {
|
|
431
548
|
items.push(code);
|
|
@@ -489,6 +606,7 @@ async function generateTags(pathOrDocument, options = {}) {
|
|
|
489
606
|
}
|
|
490
607
|
function getContext(document, options) {
|
|
491
608
|
return {
|
|
609
|
+
document,
|
|
492
610
|
renderer: {
|
|
493
611
|
...defaultRenderer,
|
|
494
612
|
...options.renderer
|