next-openapi-gen 0.0.15 → 0.0.18
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 +155 -125
- package/dist/lib/route-processor.js +3 -3
- package/dist/lib/schema-processor.js +166 -81
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,125 +1,155 @@
|
|
|
1
|
-
# next-openapi-gen
|
|
2
|
-
|
|
3
|
-
**next-openapi-gen** super fast and easy way to generate OpenAPI 3.0 documentation automatically from API routes in a Next.js 14.
|
|
4
|
-
|
|
5
|
-
With support for multiple user interfaces next-openapi-gen makes documenting your API a breeze!
|
|
6
|
-
|
|
7
|
-
## Prerequisites
|
|
8
|
-
|
|
9
|
-
- Nextjs >= 14
|
|
10
|
-
- Node >= 18
|
|
11
|
-
|
|
12
|
-
## Supported interfaces
|
|
13
|
-
|
|
14
|
-
- Swagger
|
|
15
|
-
- Redoc
|
|
16
|
-
- Stoplight Elements
|
|
17
|
-
- RapiDoc
|
|
18
|
-
|
|
19
|
-
## Features
|
|
20
|
-
|
|
21
|
-
- **Automatic OpenAPI Generation**: Generate OpenAPI 3.0 documentation from your Next.js routes, automatically parsing TypeScript types for parameters, request bodies and responses.
|
|
22
|
-
- **TypeScript Type Scanning**: Automatically resolve TypeScript types for params, body, and responses based on your API endpoint's TypeScript definitions. Field comments in TypeScript types are reflected as descriptions in the OpenAPI schema.
|
|
23
|
-
- **JSDoc-Based Documentation (Optional)**: Document API routes with JSDoc comments, including tags like `@openapi`, `@auth`, `@desc`, `@params`, `@body`, and `@response` to easily define route metadata.
|
|
24
|
-
- **UI Interface Options**: Choose between `Swagger UI`, `Redoc`, `Stoplight Elements` or `RapiDoc` to visualize your API documentation. Customize the interface to fit your preferences.
|
|
25
|
-
- **Real-time Documentation**: As your API evolves, regenerate the OpenAPI documentation with a single command, ensuring your documentation is always up to date.
|
|
26
|
-
- **Easy configuration**: Customize generator behavior using the `next.openapi.json` configuration file, allowing for quick adjustments without modifying the code.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
type
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*
|
|
79
|
-
* @
|
|
80
|
-
* @
|
|
81
|
-
* @
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
- `@
|
|
98
|
-
- `@
|
|
99
|
-
- `@
|
|
100
|
-
- `@
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
- **
|
|
123
|
-
- **
|
|
124
|
-
- **
|
|
125
|
-
- **
|
|
1
|
+
# next-openapi-gen
|
|
2
|
+
|
|
3
|
+
**next-openapi-gen** super fast and easy way to generate OpenAPI 3.0 documentation automatically from API routes in a Next.js 14.
|
|
4
|
+
|
|
5
|
+
With support for multiple user interfaces next-openapi-gen makes documenting your API a breeze!
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- Nextjs >= 14
|
|
10
|
+
- Node >= 18
|
|
11
|
+
|
|
12
|
+
## Supported interfaces
|
|
13
|
+
|
|
14
|
+
- Swagger
|
|
15
|
+
- Redoc
|
|
16
|
+
- Stoplight Elements
|
|
17
|
+
- RapiDoc
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Automatic OpenAPI Generation**: Generate OpenAPI 3.0 documentation from your Next.js routes, automatically parsing TypeScript types for parameters, request bodies and responses.
|
|
22
|
+
- **TypeScript Type Scanning**: Automatically resolve TypeScript types for params, body, and responses based on your API endpoint's TypeScript definitions. Field comments in TypeScript types are reflected as descriptions in the OpenAPI schema.
|
|
23
|
+
- **JSDoc-Based Documentation (Optional)**: Document API routes with JSDoc comments, including tags like `@openapi`, `@auth`, `@desc`, `@params`, `@body`, and `@response` to easily define route metadata.
|
|
24
|
+
- **UI Interface Options**: Choose between `Swagger UI`, `Redoc`, `Stoplight Elements` or `RapiDoc` to visualize your API documentation. Customize the interface to fit your preferences.
|
|
25
|
+
- **Real-time Documentation**: As your API evolves, regenerate the OpenAPI documentation with a single command, ensuring your documentation is always up to date.
|
|
26
|
+
- **Easy configuration**: Customize generator behavior using the `next.openapi.json` configuration file, allowing for quick adjustments without modifying the code.
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
yarn add next-openapi-gen
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Step 1: Initialize Configuration and Setup
|
|
39
|
+
|
|
40
|
+
Run the following command to generate the `next.openapi.json` configuration file and automatically set up Swagger UI with `/api-docs` routes:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx next-openapi-gen init --ui swagger --docs-url api-docs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Parameters:
|
|
47
|
+
- **ui**: `swagger` | `redoc` | `stoplight` | `rapidoc`
|
|
48
|
+
- **docs-url**: url on which api docs will be visible
|
|
49
|
+
|
|
50
|
+
This command does the following:
|
|
51
|
+
|
|
52
|
+
- Generates a `next.openapi.json` file, which stores the OpenAPI configuration for your project.
|
|
53
|
+
- Installs Swagger UI to provide an API documentation interface.
|
|
54
|
+
- Adds an `/api-docs` route in the Next.js app for visualizing the generated OpenAPI documentation.
|
|
55
|
+
|
|
56
|
+
### Step 2: Add JSDoc Comments to Your API Routes
|
|
57
|
+
|
|
58
|
+
Annotate your API routes using JSDoc comments. Here's an example:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
//app/api/auth/reset-password/route.ts
|
|
62
|
+
|
|
63
|
+
import { type NextRequest } from "next/server";
|
|
64
|
+
|
|
65
|
+
type ResetPasswordParams = {
|
|
66
|
+
token: string; // Token for resetting the password
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type ResetPasswordBody = {
|
|
70
|
+
password: string; // The new password for the user
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type ResetPasswordResponse = {
|
|
74
|
+
message: string; // Confirmation message that password has been reset
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Reset the user's password.
|
|
79
|
+
* @auth: bearer
|
|
80
|
+
* @desc: Allows users to reset their password using a reset token.
|
|
81
|
+
* @params: ResetPasswordParams
|
|
82
|
+
* @body: ResetPasswordBody
|
|
83
|
+
* @response: ResetPasswordResponse
|
|
84
|
+
*/
|
|
85
|
+
export async function POST(req: Request) {
|
|
86
|
+
const searchParams = req.nextUrl.searchParams;
|
|
87
|
+
|
|
88
|
+
const token = searchParams.get("token"); // Token from query params
|
|
89
|
+
const { password } = await req.json(); // New password from request body
|
|
90
|
+
|
|
91
|
+
// Logic to reset the user's password
|
|
92
|
+
|
|
93
|
+
return Response.json({ message: "Password has been reset" });
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `@openapi`: Marks the route for inclusion in the OpenAPI specification.
|
|
98
|
+
- `@auth`: Specifies authentication type used for API route (`basic`, `bearer`, `apikey`)
|
|
99
|
+
- `@desc`: Provides a detailed description of the API route.
|
|
100
|
+
- `@params`: Specifies the TypeScript interface for the query parameters.
|
|
101
|
+
- `@body`: Specifies the TypeScript interface for the request body.
|
|
102
|
+
- `@response`: Specifies the TypeScript interface for the response.
|
|
103
|
+
|
|
104
|
+
### Step 3: Generate the OpenAPI Specification
|
|
105
|
+
|
|
106
|
+
Run the following command to generate the OpenAPI schema based on your API routes:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx next-openapi-gen generate
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This command processes all your API routes, extracts the necessary information from JSDoc comments, and generates the OpenAPI schema, typically saved to a `swagger.json` file in the `public` folder.
|
|
113
|
+
|
|
114
|
+
### Step 4: View API Documentation
|
|
115
|
+
|
|
116
|
+
With the `/api-docs` route generated from the init command, you can now access your API documentation through Swagger UI by navigating to `http://localhost:3000/api-docs`.
|
|
117
|
+
|
|
118
|
+
## Configuration Options
|
|
119
|
+
|
|
120
|
+
The `next.openapi.json` file allows you to configure the behavior of the OpenAPI generator, including options such as:
|
|
121
|
+
|
|
122
|
+
- **apiDir**: (default: `./src/app/api`) The directory where your API routes are stored.
|
|
123
|
+
- **schemaDir**: (default: `./src`) The directory where your schema definitions are stored.
|
|
124
|
+
- **docsUrl**: (default: `./api-docs`) Route where OpenAPI UI is available.
|
|
125
|
+
- **ui**: (default: `swagger`) OpenAPI UI interface.
|
|
126
|
+
- **outputFile**: (default: `./swagger.json`) The file where the generated OpenAPI specification will be saved in `public` folder.
|
|
127
|
+
- **includeOpenApiRoutes**: (default: `false`) When `true`, the generator will only include routes that have the `@openapi` tag in their JSDoc comments.
|
|
128
|
+
|
|
129
|
+
## Interface providers
|
|
130
|
+
|
|
131
|
+
<div align="center">
|
|
132
|
+
<table>
|
|
133
|
+
<thead>
|
|
134
|
+
<th>SwaggerUI</th>
|
|
135
|
+
<th>Redoc</th>
|
|
136
|
+
<th>Stoplight Elements</th>
|
|
137
|
+
<th>RapiDoc</th>
|
|
138
|
+
</thead>
|
|
139
|
+
<tbody>
|
|
140
|
+
<tr>
|
|
141
|
+
<td>
|
|
142
|
+
<img width="320" alt="swagger" src="https://raw.githubusercontent.com/tazo90/next-openapi-gen/refs/heads/main/assets/swagger.png" alt-text="swagger">
|
|
143
|
+
</td>
|
|
144
|
+
<td>
|
|
145
|
+
<img width="320" alt="redoc" src="https://raw.githubusercontent.com/tazo90/next-openapi-gen/refs/heads/main/assets/redoc.png" alt-text="redoc">
|
|
146
|
+
</td>
|
|
147
|
+
<td>
|
|
148
|
+
<img width="320" alt="stoplight" src="https://raw.githubusercontent.com/tazo90/next-openapi-gen/refs/heads/main/assets/stoplight.png" alt-text="stoplight">
|
|
149
|
+
</td>
|
|
150
|
+
<td>
|
|
151
|
+
<img width="320" alt="rapidoc" src="https://raw.githubusercontent.com/tazo90/next-openapi-gen/refs/heads/main/assets/rapidoc.png" alt-text="rapidoc">
|
|
152
|
+
</td>
|
|
153
|
+
</tr>
|
|
154
|
+
</tbody>
|
|
155
|
+
</table>
|
|
@@ -89,7 +89,8 @@ export class RouteProcessor {
|
|
|
89
89
|
];
|
|
90
90
|
}
|
|
91
91
|
if (params) {
|
|
92
|
-
definition.parameters =
|
|
92
|
+
definition.parameters =
|
|
93
|
+
this.schemaProcessor.createRequestParamsSchema(params);
|
|
93
94
|
}
|
|
94
95
|
// Add request body
|
|
95
96
|
if (MUTATION_HTTP_METHODS.includes(method.toUpperCase())) {
|
|
@@ -117,7 +118,7 @@ export class RouteProcessor {
|
|
|
117
118
|
const aTags = Object.values(aMethods).flatMap((method) => method.tags || []);
|
|
118
119
|
// Extract tags for all methods in path b
|
|
119
120
|
const bTags = Object.values(bMethods).flatMap((method) => method.tags || []);
|
|
120
|
-
//
|
|
121
|
+
// Let's user only the first tags
|
|
121
122
|
const aPrimaryTag = aTags[0] || "";
|
|
122
123
|
const bPrimaryTag = bTags[0] || "";
|
|
123
124
|
// Sort alphabetically based on the first tag
|
|
@@ -128,7 +129,6 @@ export class RouteProcessor {
|
|
|
128
129
|
// Compare lengths of the paths
|
|
129
130
|
const aLength = a.split("/").length;
|
|
130
131
|
const bLength = b.split("/").length;
|
|
131
|
-
// Return the result of length comparison
|
|
132
132
|
return aLength - bLength; // Shorter paths come before longer ones
|
|
133
133
|
}
|
|
134
134
|
return Object.keys(paths)
|
|
@@ -5,111 +5,209 @@ import traverse from "@babel/traverse";
|
|
|
5
5
|
import * as t from "@babel/types";
|
|
6
6
|
export class SchemaProcessor {
|
|
7
7
|
schemaDir;
|
|
8
|
+
typeDefinitions = {};
|
|
9
|
+
openapiDefinitions = {};
|
|
10
|
+
contentType = "";
|
|
8
11
|
constructor(schemaDir) {
|
|
9
12
|
this.schemaDir = path.resolve(schemaDir);
|
|
10
13
|
}
|
|
11
|
-
findSchemaDefinition(schemaName) {
|
|
14
|
+
findSchemaDefinition(schemaName, contentType) {
|
|
12
15
|
let schemaNode = null;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
// assign type that is actually processed
|
|
17
|
+
this.contentType = contentType;
|
|
18
|
+
this.scanSchemaDir(this.schemaDir, schemaName);
|
|
16
19
|
return schemaNode;
|
|
17
20
|
}
|
|
18
|
-
scanSchemaDir(dir, schemaName
|
|
21
|
+
scanSchemaDir(dir, schemaName) {
|
|
19
22
|
const files = fs.readdirSync(dir);
|
|
20
23
|
files.forEach((file) => {
|
|
21
24
|
const filePath = path.join(dir, file);
|
|
22
25
|
const stat = fs.statSync(filePath);
|
|
23
26
|
if (stat.isDirectory()) {
|
|
24
|
-
this.scanSchemaDir(filePath, schemaName
|
|
27
|
+
this.scanSchemaDir(filePath, schemaName);
|
|
25
28
|
}
|
|
26
29
|
else if (file.endsWith(".ts")) {
|
|
27
|
-
this.processSchemaFile(filePath, schemaName
|
|
30
|
+
this.processSchemaFile(filePath, schemaName);
|
|
28
31
|
}
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
33
|
-
const ast = parse(content, {
|
|
34
|
-
sourceType: "module",
|
|
35
|
-
plugins: ["typescript", "decorators-legacy"],
|
|
36
|
-
});
|
|
34
|
+
collectTypeDefinitions(ast, schemaName) {
|
|
37
35
|
traverse.default(ast, {
|
|
38
36
|
VariableDeclarator: (path) => {
|
|
39
37
|
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
40
|
-
|
|
38
|
+
const name = path.node.id.name;
|
|
39
|
+
this.typeDefinitions[name] = path.node.init || path.node;
|
|
41
40
|
}
|
|
42
41
|
},
|
|
43
42
|
TSTypeAliasDeclaration: (path) => {
|
|
44
43
|
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
45
|
-
|
|
44
|
+
const name = path.node.id.name;
|
|
45
|
+
this.typeDefinitions[name] = path.node.typeAnnotation;
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
TSInterfaceDeclaration: (path) => {
|
|
49
49
|
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
50
|
-
|
|
50
|
+
const name = path.node.id.name;
|
|
51
|
+
this.typeDefinitions[name] = path.node;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
TSEnumDeclaration: (path) => {
|
|
55
|
+
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
56
|
+
const name = path.node.id.name;
|
|
57
|
+
this.typeDefinitions[name] = path.node;
|
|
51
58
|
}
|
|
52
59
|
},
|
|
53
60
|
});
|
|
54
61
|
}
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
resolveType(typeName) {
|
|
63
|
+
const typeNode = this.typeDefinitions[typeName.toString()];
|
|
64
|
+
if (!typeNode)
|
|
65
|
+
return {};
|
|
66
|
+
if (t.isTSEnumDeclaration(typeNode)) {
|
|
67
|
+
const enumValues = this.processEnum(typeNode);
|
|
68
|
+
return enumValues;
|
|
69
|
+
}
|
|
70
|
+
if (t.isTSTypeLiteral(typeNode) || t.isTSInterfaceBody(typeNode)) {
|
|
71
|
+
const properties = {};
|
|
72
|
+
if ("members" in typeNode) {
|
|
73
|
+
(typeNode.members || []).forEach((member) => {
|
|
74
|
+
if (t.isTSPropertySignature(member) && t.isIdentifier(member.key)) {
|
|
75
|
+
const propName = member.key.name;
|
|
76
|
+
const options = this.getPropertyOptions(member);
|
|
77
|
+
const property = {
|
|
78
|
+
...this.resolveTSNodeType(member.typeAnnotation?.typeAnnotation),
|
|
79
|
+
...options,
|
|
80
|
+
};
|
|
81
|
+
properties[propName] = property;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
66
84
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
return { type: "object", properties };
|
|
86
|
+
}
|
|
87
|
+
if (t.isTSArrayType(typeNode)) {
|
|
88
|
+
return {
|
|
89
|
+
type: "array",
|
|
90
|
+
items: this.resolveTSNodeType(typeNode.elementType),
|
|
70
91
|
};
|
|
71
|
-
|
|
92
|
+
}
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
resolveTSNodeType(node) {
|
|
96
|
+
if (t.isTSStringKeyword(node))
|
|
97
|
+
return { type: "string" };
|
|
98
|
+
if (t.isTSNumberKeyword(node))
|
|
99
|
+
return { type: "number" };
|
|
100
|
+
if (t.isTSBooleanKeyword(node))
|
|
101
|
+
return { type: "boolean" };
|
|
102
|
+
if (t.isTSTypeReference(node) && t.isIdentifier(node.typeName)) {
|
|
103
|
+
const typeName = node.typeName.name;
|
|
104
|
+
// Find type definition
|
|
105
|
+
this.findSchemaDefinition(typeName, this.contentType);
|
|
106
|
+
return this.resolveType(node.typeName.name);
|
|
107
|
+
}
|
|
108
|
+
if (t.isTSArrayType(node)) {
|
|
109
|
+
return {
|
|
110
|
+
type: "array",
|
|
111
|
+
items: this.resolveTSNodeType(node.elementType),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (t.isTSTypeLiteral(node)) {
|
|
115
|
+
const properties = {};
|
|
116
|
+
node.members.forEach((member) => {
|
|
117
|
+
if (t.isTSPropertySignature(member) && t.isIdentifier(member.key)) {
|
|
118
|
+
const propName = member.key.name;
|
|
119
|
+
properties[propName] = this.resolveTSNodeType(member.typeAnnotation?.typeAnnotation);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return { type: "object", properties };
|
|
123
|
+
}
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
processSchemaFile(filePath, schemaName) {
|
|
127
|
+
// Recognizes different elements of TS like variable, type, interface, enum
|
|
128
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
129
|
+
const ast = parse(content, {
|
|
130
|
+
sourceType: "module",
|
|
131
|
+
plugins: ["typescript", "decorators-legacy"],
|
|
132
|
+
});
|
|
133
|
+
this.collectTypeDefinitions(ast, schemaName);
|
|
134
|
+
const definition = this.resolveType(schemaName);
|
|
135
|
+
this.openapiDefinitions[schemaName] = definition;
|
|
136
|
+
return definition;
|
|
137
|
+
}
|
|
138
|
+
processEnum(enumNode) {
|
|
139
|
+
// Initialization OpenAPI enum object
|
|
140
|
+
const enumSchema = {
|
|
141
|
+
type: "string",
|
|
142
|
+
enum: [],
|
|
143
|
+
};
|
|
144
|
+
// Iterate throught enum members
|
|
145
|
+
enumNode.members.forEach((member) => {
|
|
146
|
+
if (t.isTSEnumMember(member)) {
|
|
72
147
|
// @ts-ignore
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
148
|
+
const name = member.id?.name;
|
|
149
|
+
// @ts-ignore
|
|
150
|
+
const value = member.initializer?.value;
|
|
151
|
+
let type = member.initializer?.type;
|
|
152
|
+
if (type === "NumericLiteral") {
|
|
153
|
+
enumSchema.type = "number";
|
|
154
|
+
}
|
|
155
|
+
const targetValue = value || name;
|
|
156
|
+
enumSchema.enum.push(targetValue);
|
|
82
157
|
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
|
|
158
|
+
});
|
|
159
|
+
return enumSchema;
|
|
160
|
+
}
|
|
161
|
+
getPropertyOptions(node) {
|
|
162
|
+
const key = node.key.name;
|
|
163
|
+
const isOptional = !!node.optional; // check if property is optional
|
|
164
|
+
const typeName = node.typeAnnotation?.typeAnnotation?.typeName?.name;
|
|
165
|
+
let description = null;
|
|
166
|
+
// get comments for field
|
|
167
|
+
if (node.trailingComments && node.trailingComments.length) {
|
|
168
|
+
description = node.trailingComments[0].value.trim(); // get first comment
|
|
169
|
+
}
|
|
170
|
+
const options = {};
|
|
171
|
+
if (description) {
|
|
172
|
+
options.description = description;
|
|
173
|
+
}
|
|
174
|
+
if (this.contentType === "params") {
|
|
175
|
+
options.required = !isOptional;
|
|
86
176
|
}
|
|
87
|
-
if (
|
|
88
|
-
|
|
177
|
+
else if (this.contentType === "body") {
|
|
178
|
+
options.nullable = isOptional;
|
|
89
179
|
}
|
|
90
|
-
return
|
|
180
|
+
return options;
|
|
91
181
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
182
|
+
createRequestParamsSchema(params) {
|
|
183
|
+
const queryParams = [];
|
|
184
|
+
if (params.properties) {
|
|
185
|
+
for (let [name, value] of Object.entries(params.properties)) {
|
|
186
|
+
const param = {
|
|
187
|
+
in: "query",
|
|
188
|
+
name,
|
|
189
|
+
schema: {
|
|
190
|
+
type: value.type,
|
|
191
|
+
},
|
|
192
|
+
required: value.required,
|
|
193
|
+
};
|
|
194
|
+
if (value.enum) {
|
|
195
|
+
param.schema.enum = value.enum;
|
|
196
|
+
}
|
|
197
|
+
if (value.description) {
|
|
198
|
+
param.description = value.description;
|
|
199
|
+
param.schema.description = value.description;
|
|
200
|
+
}
|
|
201
|
+
queryParams.push(param);
|
|
202
|
+
}
|
|
103
203
|
}
|
|
204
|
+
return queryParams;
|
|
104
205
|
}
|
|
105
206
|
createRequestBodySchema(body) {
|
|
106
207
|
return {
|
|
107
208
|
content: {
|
|
108
209
|
"application/json": {
|
|
109
|
-
schema:
|
|
110
|
-
type: "object",
|
|
111
|
-
properties: body,
|
|
112
|
-
},
|
|
210
|
+
schema: body,
|
|
113
211
|
},
|
|
114
212
|
},
|
|
115
213
|
};
|
|
@@ -120,32 +218,19 @@ export class SchemaProcessor {
|
|
|
120
218
|
description: "Successful response",
|
|
121
219
|
content: {
|
|
122
220
|
"application/json": {
|
|
123
|
-
schema:
|
|
124
|
-
type: "object",
|
|
125
|
-
properties: responses,
|
|
126
|
-
},
|
|
221
|
+
schema: responses,
|
|
127
222
|
},
|
|
128
223
|
},
|
|
129
224
|
},
|
|
130
225
|
};
|
|
131
226
|
}
|
|
132
227
|
getSchemaContent({ paramsType, bodyType, responseType }) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
: null;
|
|
140
|
-
let params = paramsSchema
|
|
141
|
-
? this.extractTypesFromSchema(paramsSchema, "params")
|
|
142
|
-
: [];
|
|
143
|
-
let body = bodySchema
|
|
144
|
-
? this.extractTypesFromSchema(bodySchema, "body")
|
|
145
|
-
: {};
|
|
146
|
-
let responses = responseSchema
|
|
147
|
-
? this.extractTypesFromSchema(responseSchema, "responses")
|
|
148
|
-
: {};
|
|
228
|
+
this.findSchemaDefinition(paramsType, "params");
|
|
229
|
+
this.findSchemaDefinition(bodyType, "body");
|
|
230
|
+
this.findSchemaDefinition(responseType, "response");
|
|
231
|
+
const params = this.openapiDefinitions[paramsType];
|
|
232
|
+
const body = this.openapiDefinitions[bodyType];
|
|
233
|
+
const responses = this.openapiDefinitions[responseType];
|
|
149
234
|
return {
|
|
150
235
|
params,
|
|
151
236
|
body,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-openapi-gen",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Super fast and easy way to generate OpenAPI documentation automatically from API routes in a NextJS 14",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"openapi",
|
|
27
27
|
"swagger",
|
|
28
28
|
"docs",
|
|
29
|
-
"api"
|
|
29
|
+
"api",
|
|
30
|
+
"redoc"
|
|
30
31
|
],
|
|
31
32
|
"publishConfig": {
|
|
32
33
|
"access": "public"
|