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 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 schema = {
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
- "application/json": {
562
- schema: body,
608
+ [detectedContentType]: {
609
+ schema: schema,
563
610
  },
564
611
  },
565
612
  };
566
613
  if (description) {
567
- schema.description = description;
614
+ requestBody.description = description;
568
615
  }
569
- return schema;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-openapi-gen",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for TypeScript types and Zod schemas.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",