next-openapi-gen 0.6.0 → 0.6.2
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/README.md +29 -0
- package/dist/lib/route-processor.js +1 -1
- package/dist/lib/schema-processor.js +53 -6
- package/dist/lib/utils.js +9 -0
- package/dist/lib/zod-converter.js +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -154,6 +154,7 @@ export async function GET(
|
|
|
154
154
|
| `@bodyDescription` | Request body description |
|
|
155
155
|
| `@response` | Response type/schema |
|
|
156
156
|
| `@responseDescription` | Response description |
|
|
157
|
+
| `@contentType` | Request body content type (`application/json`, `multipart/form-data`) |
|
|
157
158
|
| `@auth` | Authorization type (`bearer`, `basic`, `apikey`) |
|
|
158
159
|
| `@tag` | Custom tag |
|
|
159
160
|
| `@deprecated` | Marks the route as deprecated |
|
|
@@ -344,6 +345,34 @@ export async function GET() {
|
|
|
344
345
|
}
|
|
345
346
|
```
|
|
346
347
|
|
|
348
|
+
### File Uploads / Multipart Form Data
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// src/app/api/upload/route.ts
|
|
352
|
+
|
|
353
|
+
// TypeScript
|
|
354
|
+
type FileUploadFormData = {
|
|
355
|
+
file: File;
|
|
356
|
+
description?: string;
|
|
357
|
+
category: string;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Or Zod
|
|
361
|
+
const FileUploadSchema = z.object({
|
|
362
|
+
file: z.custom<File>().describe("Image file (PNG/JPG)"),
|
|
363
|
+
description: z.string().optional().describe("File description"),
|
|
364
|
+
category: z.string().describe("File category"),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* @body FileUploadSchema
|
|
369
|
+
* @contentType multipart/form-data
|
|
370
|
+
*/
|
|
371
|
+
export async function POST() {
|
|
372
|
+
// ...
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
347
376
|
## Advanced Usage
|
|
348
377
|
|
|
349
378
|
### Automatic Path Parameter Detection
|
|
@@ -160,7 +160,7 @@ export class RouteProcessor {
|
|
|
160
160
|
}
|
|
161
161
|
// Add request body
|
|
162
162
|
if (MUTATION_HTTP_METHODS.includes(method.toUpperCase())) {
|
|
163
|
-
definition.requestBody = this.schemaProcessor.createRequestBodySchema(body, bodyDescription);
|
|
163
|
+
definition.requestBody = this.schemaProcessor.createRequestBodySchema(body, bodyDescription, dataTypes.contentType);
|
|
164
164
|
}
|
|
165
165
|
// Add responses
|
|
166
166
|
definition.responses = responses
|
|
@@ -188,6 +188,9 @@ export class SchemaProcessor {
|
|
|
188
188
|
items: this.resolveTSNodeType(typeNode.elementType),
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
+
if (t.isTSUnionType(typeNode)) {
|
|
192
|
+
return this.resolveTSNodeType(typeNode);
|
|
193
|
+
}
|
|
191
194
|
return {};
|
|
192
195
|
}
|
|
193
196
|
finally {
|
|
@@ -498,6 +501,44 @@ export class SchemaProcessor {
|
|
|
498
501
|
return "example";
|
|
499
502
|
}
|
|
500
503
|
}
|
|
504
|
+
detectContentType(bodyType, explicitContentType) {
|
|
505
|
+
if (explicitContentType) {
|
|
506
|
+
return explicitContentType;
|
|
507
|
+
}
|
|
508
|
+
// Automatic detection based on type name
|
|
509
|
+
if (bodyType &&
|
|
510
|
+
(bodyType.toLowerCase().includes("formdata") ||
|
|
511
|
+
bodyType.toLowerCase().includes("fileupload") ||
|
|
512
|
+
bodyType.toLowerCase().includes("multipart"))) {
|
|
513
|
+
return "multipart/form-data";
|
|
514
|
+
}
|
|
515
|
+
return "application/json";
|
|
516
|
+
}
|
|
517
|
+
createFormDataSchema(body) {
|
|
518
|
+
if (!body.properties) {
|
|
519
|
+
return body;
|
|
520
|
+
}
|
|
521
|
+
const formDataProperties = {};
|
|
522
|
+
Object.entries(body.properties).forEach(([key, value]) => {
|
|
523
|
+
// Convert File types to binary format
|
|
524
|
+
if (value.type === "object" &&
|
|
525
|
+
(key.toLowerCase().includes("file") ||
|
|
526
|
+
value.description?.toLowerCase().includes("file"))) {
|
|
527
|
+
formDataProperties[key] = {
|
|
528
|
+
type: "string",
|
|
529
|
+
format: "binary",
|
|
530
|
+
description: value.description,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
formDataProperties[key] = value;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
return {
|
|
538
|
+
...body,
|
|
539
|
+
properties: formDataProperties,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
501
542
|
/**
|
|
502
543
|
* Create a default schema for path parameters when no schema is defined
|
|
503
544
|
*/
|
|
@@ -555,18 +596,24 @@ export class SchemaProcessor {
|
|
|
555
596
|
}
|
|
556
597
|
return queryParams;
|
|
557
598
|
}
|
|
558
|
-
createRequestBodySchema(body, description) {
|
|
559
|
-
const
|
|
599
|
+
createRequestBodySchema(body, description, contentType) {
|
|
600
|
+
const detectedContentType = this.detectContentType(body?.type || "", contentType);
|
|
601
|
+
let schema = body;
|
|
602
|
+
// If it is multipart/form-data, convert schema
|
|
603
|
+
if (detectedContentType === "multipart/form-data") {
|
|
604
|
+
schema = this.createFormDataSchema(body);
|
|
605
|
+
}
|
|
606
|
+
const requestBody = {
|
|
560
607
|
content: {
|
|
561
|
-
|
|
562
|
-
schema:
|
|
608
|
+
[detectedContentType]: {
|
|
609
|
+
schema: schema,
|
|
563
610
|
},
|
|
564
611
|
},
|
|
565
612
|
};
|
|
566
613
|
if (description) {
|
|
567
|
-
|
|
614
|
+
requestBody.description = description;
|
|
568
615
|
}
|
|
569
|
-
return
|
|
616
|
+
return requestBody;
|
|
570
617
|
}
|
|
571
618
|
createResponseSchema(responses, description) {
|
|
572
619
|
return {
|
package/dist/lib/utils.js
CHANGED
|
@@ -28,6 +28,7 @@ export function extractJSDocComments(path) {
|
|
|
28
28
|
let deprecated = false;
|
|
29
29
|
let bodyDescription = "";
|
|
30
30
|
let responseDescription = "";
|
|
31
|
+
let contentType = "";
|
|
31
32
|
if (comments) {
|
|
32
33
|
comments.forEach((comment) => {
|
|
33
34
|
const commentValue = cleanComment(comment.value);
|
|
@@ -90,6 +91,13 @@ export function extractJSDocComments(path) {
|
|
|
90
91
|
if (commentValue.includes("@response")) {
|
|
91
92
|
responseType = extractTypeFromComment(commentValue, "@response");
|
|
92
93
|
}
|
|
94
|
+
if (commentValue.includes("@contentType")) {
|
|
95
|
+
const regex = /@contentType\s*(.*)/;
|
|
96
|
+
const match = commentValue.match(regex);
|
|
97
|
+
if (match && match[1]) {
|
|
98
|
+
contentType = match[1].trim();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
93
101
|
});
|
|
94
102
|
}
|
|
95
103
|
return {
|
|
@@ -105,6 +113,7 @@ export function extractJSDocComments(path) {
|
|
|
105
113
|
deprecated,
|
|
106
114
|
bodyDescription,
|
|
107
115
|
responseDescription,
|
|
116
|
+
contentType,
|
|
108
117
|
};
|
|
109
118
|
}
|
|
110
119
|
export function extractTypeFromComment(commentValue, tag) {
|
|
@@ -846,6 +846,22 @@ export class ZodSchemaConverter {
|
|
|
846
846
|
!t.isIdentifier(node.callee.property)) {
|
|
847
847
|
return { type: "string" };
|
|
848
848
|
}
|
|
849
|
+
if (t.isMemberExpression(node.callee) &&
|
|
850
|
+
t.isIdentifier(node.callee.property)) {
|
|
851
|
+
const zodType = node.callee.property.name;
|
|
852
|
+
// Custom() support for FormData
|
|
853
|
+
if (zodType === "custom" && node.arguments.length > 0) {
|
|
854
|
+
// Check if it is FormData
|
|
855
|
+
if (t.isArrowFunctionExpression(node.arguments[0])) {
|
|
856
|
+
// Assume custom FormData validation
|
|
857
|
+
return {
|
|
858
|
+
type: "object",
|
|
859
|
+
additionalProperties: true,
|
|
860
|
+
description: "Form data object",
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
849
865
|
const zodType = node.callee.property.name;
|
|
850
866
|
let schema = {};
|
|
851
867
|
// Basic type mapping
|
package/package.json
CHANGED