docusaurus-plugin-openapi-docs 0.0.0-1105 → 0.0.0-1107
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/lib/openapi/openapi.js +17 -13
- package/lib/openapi/openapi.test.js +152 -0
- package/package.json +2 -2
- package/src/openapi/openapi.test.ts +176 -0
- package/src/openapi/openapi.ts +31 -15
package/lib/openapi/openapi.js
CHANGED
|
@@ -481,22 +481,26 @@ function createItems(openapiData, options, sidebarOptions) {
|
|
|
481
481
|
/**
|
|
482
482
|
* Attach Postman Request objects to the corresponding ApiItems.
|
|
483
483
|
*/
|
|
484
|
+
function pathTemplateToRegex(pathTemplate) {
|
|
485
|
+
const pathWithTemplateTokens = pathTemplate.replace(/\{[^}]+\}/g, "__OPENAPI_PATH_PARAM__");
|
|
486
|
+
const escapedPathTemplate = pathWithTemplateTokens.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
487
|
+
const templatePattern = escapedPathTemplate.replace(/__OPENAPI_PATH_PARAM__/g, "[^/]+");
|
|
488
|
+
return new RegExp(`^${templatePattern}$`);
|
|
489
|
+
}
|
|
484
490
|
function bindCollectionToApiItems(items, postmanCollection) {
|
|
491
|
+
const apiMatchers = items
|
|
492
|
+
.filter((item) => item.type === "api")
|
|
493
|
+
.map((item) => ({
|
|
494
|
+
apiItem: item,
|
|
495
|
+
method: item.api.method.toLowerCase(),
|
|
496
|
+
pathMatcher: pathTemplateToRegex(item.api.path),
|
|
497
|
+
}));
|
|
485
498
|
postmanCollection.forEachItem((item) => {
|
|
486
499
|
const method = item.request.method.toLowerCase();
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (item.type === "info" ||
|
|
492
|
-
item.type === "tag" ||
|
|
493
|
-
item.type === "schema") {
|
|
494
|
-
return false;
|
|
495
|
-
}
|
|
496
|
-
return item.api.path === path && item.api.method === method;
|
|
497
|
-
});
|
|
498
|
-
if ((apiItem === null || apiItem === void 0 ? void 0 : apiItem.type) === "api") {
|
|
499
|
-
apiItem.api.postman = item.request;
|
|
500
|
+
const postmanPath = item.request.url.getPath({ unresolved: true });
|
|
501
|
+
const match = apiMatchers.find(({ method: itemMethod, pathMatcher }) => itemMethod === method && pathMatcher.test(postmanPath));
|
|
502
|
+
if (match) {
|
|
503
|
+
match.apiItem.api.postman = item.request;
|
|
500
504
|
}
|
|
501
505
|
});
|
|
502
506
|
}
|
|
@@ -78,4 +78,156 @@ describe("openapi", () => {
|
|
|
78
78
|
expect(schemaItems[0].id).toBe("without-tags");
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
|
+
describe("path template and custom verb handling", () => {
|
|
82
|
+
it("binds postman requests for OpenAPI templates and path verbs", async () => {
|
|
83
|
+
const openapiData = {
|
|
84
|
+
openapi: "3.0.0",
|
|
85
|
+
info: {
|
|
86
|
+
title: "Path Template API",
|
|
87
|
+
version: "1.0.0",
|
|
88
|
+
},
|
|
89
|
+
paths: {
|
|
90
|
+
"/api/resource:customVerb": {
|
|
91
|
+
post: {
|
|
92
|
+
summary: "Custom verb endpoint",
|
|
93
|
+
operationId: "customVerbOperation",
|
|
94
|
+
responses: {
|
|
95
|
+
"200": {
|
|
96
|
+
description: "OK",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
"/api/users/{id}": {
|
|
102
|
+
get: {
|
|
103
|
+
summary: "Get user by ID",
|
|
104
|
+
operationId: "getUserById",
|
|
105
|
+
parameters: [
|
|
106
|
+
{
|
|
107
|
+
name: "id",
|
|
108
|
+
in: "path",
|
|
109
|
+
required: true,
|
|
110
|
+
schema: {
|
|
111
|
+
type: "string",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
responses: {
|
|
116
|
+
"200": {
|
|
117
|
+
description: "OK",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
"/api/users/{userId}/posts/{postId}": {
|
|
123
|
+
get: {
|
|
124
|
+
summary: "Get user post",
|
|
125
|
+
operationId: "getUserPost",
|
|
126
|
+
parameters: [
|
|
127
|
+
{
|
|
128
|
+
name: "userId",
|
|
129
|
+
in: "path",
|
|
130
|
+
required: true,
|
|
131
|
+
schema: {
|
|
132
|
+
type: "string",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "postId",
|
|
137
|
+
in: "path",
|
|
138
|
+
required: true,
|
|
139
|
+
schema: {
|
|
140
|
+
type: "string",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
responses: {
|
|
145
|
+
"200": {
|
|
146
|
+
description: "OK",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
"/files/{name}.{ext}": {
|
|
152
|
+
get: {
|
|
153
|
+
summary: "Get file by name and extension",
|
|
154
|
+
operationId: "getFileByNameAndExt",
|
|
155
|
+
parameters: [
|
|
156
|
+
{
|
|
157
|
+
name: "name",
|
|
158
|
+
in: "path",
|
|
159
|
+
required: true,
|
|
160
|
+
schema: {
|
|
161
|
+
type: "string",
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "ext",
|
|
166
|
+
in: "path",
|
|
167
|
+
required: true,
|
|
168
|
+
schema: {
|
|
169
|
+
type: "string",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
responses: {
|
|
174
|
+
"200": {
|
|
175
|
+
description: "OK",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
"/jobs/{id}:cancel": {
|
|
181
|
+
post: {
|
|
182
|
+
summary: "Cancel job",
|
|
183
|
+
operationId: "cancelJob",
|
|
184
|
+
parameters: [
|
|
185
|
+
{
|
|
186
|
+
name: "id",
|
|
187
|
+
in: "path",
|
|
188
|
+
required: true,
|
|
189
|
+
schema: {
|
|
190
|
+
type: "string",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
responses: {
|
|
195
|
+
"200": {
|
|
196
|
+
description: "OK",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
const options = {
|
|
204
|
+
specPath: "dummy",
|
|
205
|
+
outputDir: "build",
|
|
206
|
+
};
|
|
207
|
+
const sidebarOptions = {};
|
|
208
|
+
const [items] = await (0, openapi_1.processOpenapiFile)(openapiData, options, sidebarOptions);
|
|
209
|
+
const apiItems = items.filter((item) => item.type === "api");
|
|
210
|
+
expect(apiItems).toHaveLength(5);
|
|
211
|
+
const customVerbItem = apiItems.find((item) => item.type === "api" && item.id === "custom-verb-operation");
|
|
212
|
+
expect(customVerbItem.api.path).toBe("/api/resource:customVerb");
|
|
213
|
+
expect(customVerbItem.api.method).toBe("post");
|
|
214
|
+
expect(customVerbItem.api.postman).toBeDefined();
|
|
215
|
+
const standardItem = apiItems.find((item) => item.type === "api" && item.id === "get-user-by-id");
|
|
216
|
+
expect(standardItem.api.path).toBe("/api/users/{id}");
|
|
217
|
+
expect(standardItem.api.method).toBe("get");
|
|
218
|
+
expect(standardItem.api.postman).toBeDefined();
|
|
219
|
+
const multiParamItem = apiItems.find((item) => item.type === "api" && item.id === "get-user-post");
|
|
220
|
+
expect(multiParamItem.api.path).toBe("/api/users/{userId}/posts/{postId}");
|
|
221
|
+
expect(multiParamItem.api.method).toBe("get");
|
|
222
|
+
expect(multiParamItem.api.postman).toBeDefined();
|
|
223
|
+
const sameSegmentItem = apiItems.find((item) => item.type === "api" && item.id === "get-file-by-name-and-ext");
|
|
224
|
+
expect(sameSegmentItem.api.path).toBe("/files/{name}.{ext}");
|
|
225
|
+
expect(sameSegmentItem.api.method).toBe("get");
|
|
226
|
+
expect(sameSegmentItem.api.postman).toBeDefined();
|
|
227
|
+
const templatedVerbItem = apiItems.find((item) => item.type === "api" && item.id === "cancel-job");
|
|
228
|
+
expect(templatedVerbItem.api.path).toBe("/jobs/{id}:cancel");
|
|
229
|
+
expect(templatedVerbItem.api.method).toBe("post");
|
|
230
|
+
expect(templatedVerbItem.api.postman).toBeDefined();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
81
233
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docusaurus-plugin-openapi-docs",
|
|
3
3
|
"description": "OpenAPI plugin for Docusaurus.",
|
|
4
|
-
"version": "0.0.0-
|
|
4
|
+
"version": "0.0.0-1107",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"openapi",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=14"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "50a1285465efa4833527f43efa0d2a33ea1c03f6"
|
|
69
69
|
}
|
|
@@ -95,4 +95,180 @@ describe("openapi", () => {
|
|
|
95
95
|
expect(schemaItems[0].id).toBe("without-tags");
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
|
+
|
|
99
|
+
describe("path template and custom verb handling", () => {
|
|
100
|
+
it("binds postman requests for OpenAPI templates and path verbs", async () => {
|
|
101
|
+
const openapiData = {
|
|
102
|
+
openapi: "3.0.0",
|
|
103
|
+
info: {
|
|
104
|
+
title: "Path Template API",
|
|
105
|
+
version: "1.0.0",
|
|
106
|
+
},
|
|
107
|
+
paths: {
|
|
108
|
+
"/api/resource:customVerb": {
|
|
109
|
+
post: {
|
|
110
|
+
summary: "Custom verb endpoint",
|
|
111
|
+
operationId: "customVerbOperation",
|
|
112
|
+
responses: {
|
|
113
|
+
"200": {
|
|
114
|
+
description: "OK",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
"/api/users/{id}": {
|
|
120
|
+
get: {
|
|
121
|
+
summary: "Get user by ID",
|
|
122
|
+
operationId: "getUserById",
|
|
123
|
+
parameters: [
|
|
124
|
+
{
|
|
125
|
+
name: "id",
|
|
126
|
+
in: "path",
|
|
127
|
+
required: true,
|
|
128
|
+
schema: {
|
|
129
|
+
type: "string",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
responses: {
|
|
134
|
+
"200": {
|
|
135
|
+
description: "OK",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
"/api/users/{userId}/posts/{postId}": {
|
|
141
|
+
get: {
|
|
142
|
+
summary: "Get user post",
|
|
143
|
+
operationId: "getUserPost",
|
|
144
|
+
parameters: [
|
|
145
|
+
{
|
|
146
|
+
name: "userId",
|
|
147
|
+
in: "path",
|
|
148
|
+
required: true,
|
|
149
|
+
schema: {
|
|
150
|
+
type: "string",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "postId",
|
|
155
|
+
in: "path",
|
|
156
|
+
required: true,
|
|
157
|
+
schema: {
|
|
158
|
+
type: "string",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
responses: {
|
|
163
|
+
"200": {
|
|
164
|
+
description: "OK",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
"/files/{name}.{ext}": {
|
|
170
|
+
get: {
|
|
171
|
+
summary: "Get file by name and extension",
|
|
172
|
+
operationId: "getFileByNameAndExt",
|
|
173
|
+
parameters: [
|
|
174
|
+
{
|
|
175
|
+
name: "name",
|
|
176
|
+
in: "path",
|
|
177
|
+
required: true,
|
|
178
|
+
schema: {
|
|
179
|
+
type: "string",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "ext",
|
|
184
|
+
in: "path",
|
|
185
|
+
required: true,
|
|
186
|
+
schema: {
|
|
187
|
+
type: "string",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
responses: {
|
|
192
|
+
"200": {
|
|
193
|
+
description: "OK",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
"/jobs/{id}:cancel": {
|
|
199
|
+
post: {
|
|
200
|
+
summary: "Cancel job",
|
|
201
|
+
operationId: "cancelJob",
|
|
202
|
+
parameters: [
|
|
203
|
+
{
|
|
204
|
+
name: "id",
|
|
205
|
+
in: "path",
|
|
206
|
+
required: true,
|
|
207
|
+
schema: {
|
|
208
|
+
type: "string",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
responses: {
|
|
213
|
+
"200": {
|
|
214
|
+
description: "OK",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const options: APIOptions = {
|
|
223
|
+
specPath: "dummy",
|
|
224
|
+
outputDir: "build",
|
|
225
|
+
};
|
|
226
|
+
const sidebarOptions = {} as SidebarOptions;
|
|
227
|
+
const [items] = await processOpenapiFile(
|
|
228
|
+
openapiData as any,
|
|
229
|
+
options,
|
|
230
|
+
sidebarOptions
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const apiItems = items.filter((item) => item.type === "api");
|
|
234
|
+
expect(apiItems).toHaveLength(5);
|
|
235
|
+
|
|
236
|
+
const customVerbItem = apiItems.find(
|
|
237
|
+
(item) => item.type === "api" && item.id === "custom-verb-operation"
|
|
238
|
+
) as any;
|
|
239
|
+
expect(customVerbItem.api.path).toBe("/api/resource:customVerb");
|
|
240
|
+
expect(customVerbItem.api.method).toBe("post");
|
|
241
|
+
expect(customVerbItem.api.postman).toBeDefined();
|
|
242
|
+
|
|
243
|
+
const standardItem = apiItems.find(
|
|
244
|
+
(item) => item.type === "api" && item.id === "get-user-by-id"
|
|
245
|
+
) as any;
|
|
246
|
+
expect(standardItem.api.path).toBe("/api/users/{id}");
|
|
247
|
+
expect(standardItem.api.method).toBe("get");
|
|
248
|
+
expect(standardItem.api.postman).toBeDefined();
|
|
249
|
+
|
|
250
|
+
const multiParamItem = apiItems.find(
|
|
251
|
+
(item) => item.type === "api" && item.id === "get-user-post"
|
|
252
|
+
) as any;
|
|
253
|
+
expect(multiParamItem.api.path).toBe(
|
|
254
|
+
"/api/users/{userId}/posts/{postId}"
|
|
255
|
+
);
|
|
256
|
+
expect(multiParamItem.api.method).toBe("get");
|
|
257
|
+
expect(multiParamItem.api.postman).toBeDefined();
|
|
258
|
+
|
|
259
|
+
const sameSegmentItem = apiItems.find(
|
|
260
|
+
(item) => item.type === "api" && item.id === "get-file-by-name-and-ext"
|
|
261
|
+
) as any;
|
|
262
|
+
expect(sameSegmentItem.api.path).toBe("/files/{name}.{ext}");
|
|
263
|
+
expect(sameSegmentItem.api.method).toBe("get");
|
|
264
|
+
expect(sameSegmentItem.api.postman).toBeDefined();
|
|
265
|
+
|
|
266
|
+
const templatedVerbItem = apiItems.find(
|
|
267
|
+
(item) => item.type === "api" && item.id === "cancel-job"
|
|
268
|
+
) as any;
|
|
269
|
+
expect(templatedVerbItem.api.path).toBe("/jobs/{id}:cancel");
|
|
270
|
+
expect(templatedVerbItem.api.method).toBe("post");
|
|
271
|
+
expect(templatedVerbItem.api.postman).toBeDefined();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
98
274
|
});
|
package/src/openapi/openapi.ts
CHANGED
|
@@ -561,28 +561,44 @@ function createItems(
|
|
|
561
561
|
/**
|
|
562
562
|
* Attach Postman Request objects to the corresponding ApiItems.
|
|
563
563
|
*/
|
|
564
|
+
function pathTemplateToRegex(pathTemplate: string): RegExp {
|
|
565
|
+
const pathWithTemplateTokens = pathTemplate.replace(
|
|
566
|
+
/\{[^}]+\}/g,
|
|
567
|
+
"__OPENAPI_PATH_PARAM__"
|
|
568
|
+
);
|
|
569
|
+
const escapedPathTemplate = pathWithTemplateTokens.replace(
|
|
570
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
571
|
+
"\\$&"
|
|
572
|
+
);
|
|
573
|
+
const templatePattern = escapedPathTemplate.replace(
|
|
574
|
+
/__OPENAPI_PATH_PARAM__/g,
|
|
575
|
+
"[^/]+"
|
|
576
|
+
);
|
|
577
|
+
return new RegExp(`^${templatePattern}$`);
|
|
578
|
+
}
|
|
579
|
+
|
|
564
580
|
function bindCollectionToApiItems(
|
|
565
581
|
items: ApiMetadata[],
|
|
566
582
|
postmanCollection: sdk.Collection
|
|
567
583
|
) {
|
|
584
|
+
const apiMatchers = items
|
|
585
|
+
.filter((item): item is ApiPageMetadata => item.type === "api")
|
|
586
|
+
.map((item) => ({
|
|
587
|
+
apiItem: item,
|
|
588
|
+
method: item.api.method.toLowerCase(),
|
|
589
|
+
pathMatcher: pathTemplateToRegex(item.api.path),
|
|
590
|
+
}));
|
|
591
|
+
|
|
568
592
|
postmanCollection.forEachItem((item: any) => {
|
|
569
593
|
const method = item.request.method.toLowerCase();
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
item.type === "info" ||
|
|
576
|
-
item.type === "tag" ||
|
|
577
|
-
item.type === "schema"
|
|
578
|
-
) {
|
|
579
|
-
return false;
|
|
580
|
-
}
|
|
581
|
-
return item.api.path === path && item.api.method === method;
|
|
582
|
-
});
|
|
594
|
+
const postmanPath = item.request.url.getPath({ unresolved: true });
|
|
595
|
+
const match = apiMatchers.find(
|
|
596
|
+
({ method: itemMethod, pathMatcher }) =>
|
|
597
|
+
itemMethod === method && pathMatcher.test(postmanPath)
|
|
598
|
+
);
|
|
583
599
|
|
|
584
|
-
if (
|
|
585
|
-
apiItem.api.postman = item.request;
|
|
600
|
+
if (match) {
|
|
601
|
+
match.apiItem.api.postman = item.request;
|
|
586
602
|
}
|
|
587
603
|
});
|
|
588
604
|
}
|