@gitbook/react-openapi 0.7.1 → 1.0.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/CHANGELOG.md +41 -0
- package/dist/InteractiveSection.d.ts +4 -6
- package/dist/InteractiveSection.jsx +96 -0
- package/dist/Markdown.d.ts +1 -2
- package/dist/Markdown.jsx +5 -0
- package/dist/OpenAPICodeSample.d.ts +2 -4
- package/dist/OpenAPICodeSample.jsx +143 -0
- package/dist/OpenAPIDisclosure.d.ts +12 -0
- package/dist/OpenAPIDisclosure.jsx +32 -0
- package/dist/OpenAPIDisclosureGroup.d.ts +19 -0
- package/dist/OpenAPIDisclosureGroup.jsx +81 -0
- package/dist/OpenAPIOperation.d.ts +2 -4
- package/dist/OpenAPIOperation.jsx +51 -0
- package/dist/OpenAPIOperationContext.d.ts +16 -0
- package/dist/OpenAPIOperationContext.jsx +26 -0
- package/dist/OpenAPIPath.d.ts +8 -0
- package/dist/OpenAPIPath.jsx +54 -0
- package/dist/OpenAPIRequestBody.d.ts +3 -4
- package/dist/OpenAPIRequestBody.jsx +19 -0
- package/dist/OpenAPIResponse.d.ts +4 -4
- package/dist/OpenAPIResponse.jsx +49 -0
- package/dist/OpenAPIResponseExample.d.ts +2 -4
- package/dist/OpenAPIResponseExample.jsx +108 -0
- package/dist/OpenAPIResponses.d.ts +3 -4
- package/dist/OpenAPIResponses.jsx +36 -0
- package/dist/OpenAPISchema.d.ts +11 -8
- package/dist/OpenAPISchema.jsx +295 -0
- package/dist/OpenAPISchemaName.d.ts +12 -0
- package/dist/OpenAPISchemaName.jsx +15 -0
- package/dist/OpenAPISecurities.d.ts +2 -4
- package/dist/OpenAPISecurities.jsx +55 -0
- package/dist/OpenAPIServerURL.d.ts +2 -3
- package/dist/OpenAPIServerURL.jsx +67 -0
- package/dist/OpenAPIServerURLVariable.d.ts +2 -3
- package/dist/OpenAPIServerURLVariable.jsx +8 -0
- package/dist/OpenAPISpec.d.ts +3 -4
- package/dist/OpenAPISpec.jsx +91 -0
- package/dist/OpenAPITabs.d.ts +25 -0
- package/dist/OpenAPITabs.jsx +67 -0
- package/dist/ScalarApiButton.d.ts +3 -3
- package/dist/ScalarApiButton.jsx +51 -0
- package/dist/code-samples.d.ts +4 -0
- package/dist/code-samples.js +103 -38
- package/dist/fetchOpenAPIOperation.d.ts +9 -54
- package/dist/fetchOpenAPIOperation.js +178 -107
- package/dist/generateSchemaExample.d.ts +2 -2
- package/dist/generateSchemaExample.js +28 -100
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/resolveOpenAPIOperation.d.ts +11 -0
- package/dist/resolveOpenAPIOperation.js +194 -0
- package/dist/stringifyOpenAPI.d.ts +4 -0
- package/dist/stringifyOpenAPI.js +6 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +11 -12
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +15 -2
- package/package.json +11 -10
- package/src/InteractiveSection.tsx +119 -78
- package/src/Markdown.tsx +2 -3
- package/src/OpenAPICodeSample.tsx +35 -21
- package/src/OpenAPIDisclosure.tsx +50 -0
- package/src/OpenAPIDisclosureGroup.tsx +136 -0
- package/src/OpenAPIOperation.tsx +36 -42
- package/src/OpenAPIOperationContext.tsx +45 -0
- package/src/OpenAPIPath.tsx +65 -0
- package/src/OpenAPIRequestBody.tsx +3 -14
- package/src/OpenAPIResponse.tsx +39 -43
- package/src/OpenAPIResponseExample.tsx +89 -31
- package/src/OpenAPIResponses.tsx +51 -15
- package/src/OpenAPISchema.test.ts +1 -1
- package/src/OpenAPISchema.tsx +124 -92
- package/src/OpenAPISchemaName.tsx +27 -0
- package/src/OpenAPISecurities.tsx +45 -24
- package/src/OpenAPIServerURL.tsx +17 -10
- package/src/OpenAPIServerURLVariable.tsx +2 -4
- package/src/OpenAPISpec.tsx +56 -53
- package/src/OpenAPITabs.tsx +113 -0
- package/src/ScalarApiButton.tsx +84 -7
- package/src/code-samples.test.ts +51 -0
- package/src/code-samples.ts +95 -31
- package/src/generateSchemaExample.ts +25 -151
- package/src/index.ts +3 -2
- package/src/resolveOpenAPIOperation.test.ts +177 -0
- package/src/resolveOpenAPIOperation.ts +163 -0
- package/src/stringifyOpenAPI.ts +6 -0
- package/src/types.ts +17 -10
- package/src/utils.ts +17 -2
- package/dist/InteractiveSection.js +0 -47
- package/dist/Markdown.js +0 -6
- package/dist/OpenAPICodeSample.js +0 -110
- package/dist/OpenAPIOperation.js +0 -38
- package/dist/OpenAPIRequestBody.js +0 -18
- package/dist/OpenAPIResponse.js +0 -32
- package/dist/OpenAPIResponseExample.js +0 -54
- package/dist/OpenAPIResponses.js +0 -18
- package/dist/OpenAPISchema.js +0 -235
- package/dist/OpenAPISchema.test.d.ts +0 -1
- package/dist/OpenAPISchema.test.js +0 -91
- package/dist/OpenAPISecurities.js +0 -42
- package/dist/OpenAPIServerURL.js +0 -51
- package/dist/OpenAPIServerURLVariable.js +0 -10
- package/dist/OpenAPISpec.js +0 -70
- package/dist/ScalarApiButton.js +0 -14
- package/dist/fetchOpenAPIOperation.test.d.ts +0 -1
- package/dist/fetchOpenAPIOperation.test.js +0 -152
- package/dist/resolveOpenAPIPath.d.ts +0 -7
- package/dist/resolveOpenAPIPath.js +0 -112
- package/dist/resolveOpenAPIPath.test.d.ts +0 -1
- package/dist/resolveOpenAPIPath.test.js +0 -39
- package/src/fetchOpenAPIOperation.test.ts +0 -185
- package/src/fetchOpenAPIOperation.ts +0 -230
- package/src/resolveOpenAPIPath.test.ts +0 -60
- package/src/resolveOpenAPIPath.ts +0 -145
package/dist/OpenAPIServerURL.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
|
|
3
|
-
/**
|
|
4
|
-
* Show the url of the server with variables replaced by their default values.
|
|
5
|
-
*/
|
|
6
|
-
export function OpenAPIServerURL(props) {
|
|
7
|
-
const { servers } = props;
|
|
8
|
-
const server = servers[0];
|
|
9
|
-
const parts = parseServerURL(server?.url ?? '');
|
|
10
|
-
return (React.createElement("span", null, parts.map((part, i) => {
|
|
11
|
-
if (part.kind === 'text') {
|
|
12
|
-
return React.createElement("span", { key: i }, part.text);
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
if (!server.variables?.[part.name]) {
|
|
16
|
-
return React.createElement("span", { key: i }, `{${part.name}}`);
|
|
17
|
-
}
|
|
18
|
-
return (React.createElement(OpenAPIServerURLVariable, { key: i, name: part.name, variable: server.variables[part.name] }));
|
|
19
|
-
}
|
|
20
|
-
})));
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Get the default URL for the server.
|
|
24
|
-
*/
|
|
25
|
-
export function getServersURL(servers) {
|
|
26
|
-
const server = servers[0];
|
|
27
|
-
const parts = parseServerURL(server?.url ?? '');
|
|
28
|
-
return parts
|
|
29
|
-
.map((part) => {
|
|
30
|
-
if (part.kind === 'text') {
|
|
31
|
-
return part.text;
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
return server.variables?.[part.name]?.default ?? `{${part.name}}`;
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
.join('');
|
|
38
|
-
}
|
|
39
|
-
function parseServerURL(url) {
|
|
40
|
-
const parts = url.split(/{([^}]+)}/g);
|
|
41
|
-
const result = [];
|
|
42
|
-
for (let i = 0; i < parts.length; i++) {
|
|
43
|
-
if (i % 2 === 0) {
|
|
44
|
-
result.push({ kind: 'text', text: parts[i] });
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
result.push({ kind: 'variable', name: parts[i] });
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import classNames from 'classnames';
|
|
4
|
-
/**
|
|
5
|
-
* Interactive component to show the value of a server variable and let the user change it.
|
|
6
|
-
*/
|
|
7
|
-
export function OpenAPIServerURLVariable(props) {
|
|
8
|
-
const { variable } = props;
|
|
9
|
-
return React.createElement("span", { className: classNames('openapi-url-var') }, variable.default);
|
|
10
|
-
}
|
package/dist/OpenAPISpec.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { fromJSON } from './fetchOpenAPIOperation';
|
|
4
|
-
import { InteractiveSection } from './InteractiveSection';
|
|
5
|
-
import { OpenAPIRequestBody } from './OpenAPIRequestBody';
|
|
6
|
-
import { OpenAPIResponses } from './OpenAPIResponses';
|
|
7
|
-
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
|
8
|
-
import { OpenAPISecurities } from './OpenAPISecurities';
|
|
9
|
-
import { noReference } from './utils';
|
|
10
|
-
/**
|
|
11
|
-
* Client component to render the spec for the request and response.
|
|
12
|
-
*
|
|
13
|
-
* We use a client component as rendering recursive JSON schema in the server is expensive
|
|
14
|
-
* (the entire schema is rendered at once, while the client component only renders the visible part)
|
|
15
|
-
*/
|
|
16
|
-
export function OpenAPISpec(props) {
|
|
17
|
-
const { rawData, context } = props;
|
|
18
|
-
const parsedData = fromJSON(rawData);
|
|
19
|
-
const { operation, securities } = parsedData;
|
|
20
|
-
const parameterGroups = groupParameters((operation.parameters || []).map(noReference));
|
|
21
|
-
return (React.createElement(React.Fragment, null,
|
|
22
|
-
securities.length > 0 ? (React.createElement(OpenAPISecurities, { securities: securities, context: context })) : null,
|
|
23
|
-
parameterGroups.map((group) => (React.createElement(InteractiveSection, { key: group.key, className: "openapi-parameters", toggeable: true, toggleOpenIcon: context.icons.chevronRight, toggleCloseIcon: context.icons.chevronDown, header: group.label, defaultOpened: group.key === 'path' || context.defaultInteractiveOpened },
|
|
24
|
-
React.createElement(OpenAPISchemaProperties, { properties: group.parameters.map((parameter) => ({
|
|
25
|
-
propertyName: parameter.name,
|
|
26
|
-
schema: {
|
|
27
|
-
// Description of the parameter is defined at the parameter level
|
|
28
|
-
// we use display it if the schema doesn't override it
|
|
29
|
-
description: parameter.description,
|
|
30
|
-
example: parameter.example,
|
|
31
|
-
...(noReference(parameter.schema) ?? {}),
|
|
32
|
-
},
|
|
33
|
-
required: parameter.required,
|
|
34
|
-
})), context: context })))),
|
|
35
|
-
operation.requestBody ? (React.createElement(OpenAPIRequestBody, { requestBody: noReference(operation.requestBody), context: context })) : null,
|
|
36
|
-
operation.responses ? (React.createElement(OpenAPIResponses, { responses: noReference(operation.responses), context: context })) : null));
|
|
37
|
-
}
|
|
38
|
-
function groupParameters(parameters) {
|
|
39
|
-
const sorted = ['path', 'query', 'header'];
|
|
40
|
-
const groups = [];
|
|
41
|
-
parameters.forEach((parameter) => {
|
|
42
|
-
const key = parameter.in;
|
|
43
|
-
const label = getParameterGroupName(parameter.in);
|
|
44
|
-
const group = groups.find((group) => group.key === key);
|
|
45
|
-
if (group) {
|
|
46
|
-
group.parameters.push(parameter);
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
groups.push({
|
|
50
|
-
key,
|
|
51
|
-
label,
|
|
52
|
-
parameters: [parameter],
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
groups.sort((a, b) => sorted.indexOf(a.key) - sorted.indexOf(b.key));
|
|
57
|
-
return groups;
|
|
58
|
-
}
|
|
59
|
-
function getParameterGroupName(paramIn) {
|
|
60
|
-
switch (paramIn) {
|
|
61
|
-
case 'path':
|
|
62
|
-
return 'Path parameters';
|
|
63
|
-
case 'query':
|
|
64
|
-
return 'Query parameters';
|
|
65
|
-
case 'header':
|
|
66
|
-
return 'Header parameters';
|
|
67
|
-
default:
|
|
68
|
-
return paramIn;
|
|
69
|
-
}
|
|
70
|
-
}
|
package/dist/ScalarApiButton.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useApiClientModal } from '@scalar/api-client-react';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
/**
|
|
5
|
-
* Button which launches the Scalar API Client
|
|
6
|
-
*/
|
|
7
|
-
export function ScalarApiButton({ method, path }) {
|
|
8
|
-
const client = useApiClientModal();
|
|
9
|
-
return (React.createElement("div", { className: "scalar scalar-activate" },
|
|
10
|
-
React.createElement("button", { className: "scalar-activate-button", onClick: () => client?.open({ method, path }) },
|
|
11
|
-
React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "10", height: "12", fill: "none" },
|
|
12
|
-
React.createElement("path", { stroke: "currentColor", strokeWidth: "1.5", d: "M1 10.05V1.43c0-.2.2-.31.37-.22l7.26 4.08c.17.1.17.33.01.43l-7.26 4.54a.25.25 0 0 1-.38-.21Z" })),
|
|
13
|
-
"Test it")));
|
|
14
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { it, expect } from 'bun:test';
|
|
2
|
-
import { fetchOpenAPIOperation, parseOpenAPIV3 } from './fetchOpenAPIOperation';
|
|
3
|
-
const fetcher = {
|
|
4
|
-
fetch: async (url) => {
|
|
5
|
-
const response = await fetch(url);
|
|
6
|
-
return parseOpenAPIV3(url, await response.text());
|
|
7
|
-
},
|
|
8
|
-
};
|
|
9
|
-
it('should resolve refs', async () => {
|
|
10
|
-
const resolved = await fetchOpenAPIOperation({
|
|
11
|
-
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
|
12
|
-
method: 'put',
|
|
13
|
-
path: '/pet',
|
|
14
|
-
}, fetcher);
|
|
15
|
-
expect(resolved).toMatchObject({
|
|
16
|
-
servers: [
|
|
17
|
-
{
|
|
18
|
-
url: '/api/v3',
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
operation: {
|
|
22
|
-
tags: ['pet'],
|
|
23
|
-
summary: 'Update an existing pet',
|
|
24
|
-
description: 'Update an existing pet by Id',
|
|
25
|
-
requestBody: {
|
|
26
|
-
content: {
|
|
27
|
-
'application/json': {
|
|
28
|
-
schema: {
|
|
29
|
-
type: 'object',
|
|
30
|
-
required: ['name', 'photoUrls'],
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
it('should support yaml', async () => {
|
|
39
|
-
const resolved = await fetchOpenAPIOperation({
|
|
40
|
-
url: 'https://petstore3.swagger.io/api/v3/openapi.yaml',
|
|
41
|
-
method: 'put',
|
|
42
|
-
path: '/pet',
|
|
43
|
-
}, fetcher);
|
|
44
|
-
expect(resolved).toMatchObject({
|
|
45
|
-
servers: [
|
|
46
|
-
{
|
|
47
|
-
url: '/api/v3',
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
operation: {
|
|
51
|
-
tags: ['pet'],
|
|
52
|
-
summary: 'Update an existing pet',
|
|
53
|
-
description: 'Update an existing pet by Id',
|
|
54
|
-
requestBody: {
|
|
55
|
-
content: {
|
|
56
|
-
'application/json': {
|
|
57
|
-
schema: {
|
|
58
|
-
type: 'object',
|
|
59
|
-
required: ['name', 'photoUrls'],
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
it('should resolve circular refs', async () => {
|
|
68
|
-
const resolved = await fetchOpenAPIOperation({
|
|
69
|
-
url: 'https://api.gitbook.com/openapi.json',
|
|
70
|
-
method: 'post',
|
|
71
|
-
path: '/search/ask',
|
|
72
|
-
}, fetcher);
|
|
73
|
-
expect(resolved).toMatchObject({
|
|
74
|
-
servers: [
|
|
75
|
-
{
|
|
76
|
-
url: '{host}/v1',
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
operation: {
|
|
80
|
-
operationId: 'askQuery',
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
it('should resolve to null if the method is not supported', async () => {
|
|
85
|
-
const resolved = await fetchOpenAPIOperation({
|
|
86
|
-
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
|
87
|
-
method: 'dontexist',
|
|
88
|
-
path: '/pet',
|
|
89
|
-
}, fetcher);
|
|
90
|
-
expect(resolved).toBe(null);
|
|
91
|
-
});
|
|
92
|
-
it('should parse Swagger 2.0', async () => {
|
|
93
|
-
const resolved = await fetchOpenAPIOperation({
|
|
94
|
-
url: 'https://petstore.swagger.io/v2/swagger.json',
|
|
95
|
-
method: 'put',
|
|
96
|
-
path: '/pet',
|
|
97
|
-
}, fetcher);
|
|
98
|
-
expect(resolved).toMatchObject({
|
|
99
|
-
servers: [
|
|
100
|
-
{
|
|
101
|
-
url: 'https://petstore.swagger.io/v2',
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
url: 'http://petstore.swagger.io/v2',
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
operation: {
|
|
108
|
-
tags: ['pet'],
|
|
109
|
-
summary: 'Update an existing pet',
|
|
110
|
-
description: '',
|
|
111
|
-
requestBody: {
|
|
112
|
-
content: {
|
|
113
|
-
'application/json': {
|
|
114
|
-
schema: {
|
|
115
|
-
type: 'object',
|
|
116
|
-
required: ['name', 'photoUrls'],
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
it('should resolve a ref with whitespace', async () => {
|
|
125
|
-
const resolved = await fetchOpenAPIOperation({
|
|
126
|
-
url: ' https://petstore3.swagger.io/api/v3/openapi.json',
|
|
127
|
-
method: 'put',
|
|
128
|
-
path: '/pet',
|
|
129
|
-
}, fetcher);
|
|
130
|
-
expect(resolved).toMatchObject({
|
|
131
|
-
servers: [
|
|
132
|
-
{
|
|
133
|
-
url: '/api/v3',
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
operation: {
|
|
137
|
-
tags: ['pet'],
|
|
138
|
-
summary: 'Update an existing pet',
|
|
139
|
-
description: 'Update an existing pet by Id',
|
|
140
|
-
requestBody: {
|
|
141
|
-
content: {
|
|
142
|
-
'application/json': {
|
|
143
|
-
schema: {
|
|
144
|
-
type: 'object',
|
|
145
|
-
required: ['name', 'photoUrls'],
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { OpenAPIFetcher } from './types';
|
|
2
|
-
export declare const SYMBOL_REF_RESOLVED = "__$refResolved";
|
|
3
|
-
/**
|
|
4
|
-
* Resolve a path in a OpenAPI file.
|
|
5
|
-
* It resolves any reference needed to resolve the path, ignoring other references outside the path.
|
|
6
|
-
*/
|
|
7
|
-
export declare function resolveOpenAPIPath<T>(url: string, dataPath: string[], fetcher: OpenAPIFetcher): Promise<T | undefined>;
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
const SYMBOL_MARKDOWN_PARSED = '__$markdownParsed';
|
|
2
|
-
export const SYMBOL_REF_RESOLVED = '__$refResolved';
|
|
3
|
-
/**
|
|
4
|
-
* Resolve a path in a OpenAPI file.
|
|
5
|
-
* It resolves any reference needed to resolve the path, ignoring other references outside the path.
|
|
6
|
-
*/
|
|
7
|
-
export async function resolveOpenAPIPath(url, dataPath, fetcher) {
|
|
8
|
-
const data = await fetcher.fetch(url);
|
|
9
|
-
let value = data;
|
|
10
|
-
if (!value) {
|
|
11
|
-
return undefined;
|
|
12
|
-
}
|
|
13
|
-
const lastKey = dataPath[dataPath.length - 1];
|
|
14
|
-
dataPath = dataPath.slice(0, -1);
|
|
15
|
-
for (const part of dataPath) {
|
|
16
|
-
// @ts-ignore
|
|
17
|
-
if (isRef(value[part])) {
|
|
18
|
-
await transformAll(url, value, part, fetcher);
|
|
19
|
-
}
|
|
20
|
-
// @ts-ignore
|
|
21
|
-
value = value[part];
|
|
22
|
-
// If any part along the path is undefined, return undefined.
|
|
23
|
-
if (typeof value !== 'object' || value === null) {
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
await transformAll(url, value, lastKey, fetcher);
|
|
28
|
-
// @ts-expect-error
|
|
29
|
-
return value[lastKey];
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Recursively process a part of the OpenAPI spec to resolve all references.
|
|
33
|
-
*/
|
|
34
|
-
async function transformAll(url, data, key, fetcher) {
|
|
35
|
-
const value = data[key];
|
|
36
|
-
if (typeof value === 'string' &&
|
|
37
|
-
key === 'description' &&
|
|
38
|
-
fetcher.parseMarkdown &&
|
|
39
|
-
!data[SYMBOL_MARKDOWN_PARSED]) {
|
|
40
|
-
// Parse markdown
|
|
41
|
-
data[SYMBOL_MARKDOWN_PARSED] = true;
|
|
42
|
-
data[key] = await fetcher.parseMarkdown(value);
|
|
43
|
-
}
|
|
44
|
-
else if (typeof value === 'string' ||
|
|
45
|
-
typeof value === 'number' ||
|
|
46
|
-
typeof value === 'boolean' ||
|
|
47
|
-
value === null) {
|
|
48
|
-
// Primitives
|
|
49
|
-
}
|
|
50
|
-
else if (typeof value === 'object' && value !== null && SYMBOL_REF_RESOLVED in value) {
|
|
51
|
-
// Ref was already resolved
|
|
52
|
-
}
|
|
53
|
-
else if (isRef(value)) {
|
|
54
|
-
const ref = value.$ref;
|
|
55
|
-
// Delete the ref to avoid infinite loop with circular references
|
|
56
|
-
// @ts-ignore
|
|
57
|
-
delete value.$ref;
|
|
58
|
-
data[key] = await resolveReference(url, ref, fetcher);
|
|
59
|
-
if (data[key]) {
|
|
60
|
-
data[key][SYMBOL_REF_RESOLVED] = extractRefName(ref);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else if (Array.isArray(value)) {
|
|
64
|
-
// Recursively resolve all references in the array
|
|
65
|
-
await Promise.all(value.map((item, index) => transformAll(url, value, index, fetcher)));
|
|
66
|
-
}
|
|
67
|
-
else if (typeof value === 'object' && value !== null) {
|
|
68
|
-
// Recursively resolve all references in the object
|
|
69
|
-
const keys = Object.keys(value);
|
|
70
|
-
for (const key of keys) {
|
|
71
|
-
await transformAll(url, value, key, fetcher);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
async function resolveReference(origin, ref, fetcher) {
|
|
76
|
-
const parsed = parseReference(origin, ref);
|
|
77
|
-
return resolveOpenAPIPath(parsed.url, parsed.dataPath, fetcher);
|
|
78
|
-
}
|
|
79
|
-
function parseReference(origin, ref) {
|
|
80
|
-
if (!ref) {
|
|
81
|
-
return {
|
|
82
|
-
url: origin,
|
|
83
|
-
dataPath: [],
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
if (ref.startsWith('#')) {
|
|
87
|
-
// Local references
|
|
88
|
-
const dataPath = ref.split('/').filter(Boolean).slice(1);
|
|
89
|
-
return {
|
|
90
|
-
url: origin,
|
|
91
|
-
dataPath,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
// Absolute references
|
|
95
|
-
const url = new URL(ref, origin);
|
|
96
|
-
if (url.hash) {
|
|
97
|
-
const hash = url.hash;
|
|
98
|
-
url.hash = '';
|
|
99
|
-
return parseReference(url.toString(), hash);
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
url: url.toString(),
|
|
103
|
-
dataPath: [],
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function extractRefName(ref) {
|
|
107
|
-
const parts = ref.split('/');
|
|
108
|
-
return parts[parts.length - 1];
|
|
109
|
-
}
|
|
110
|
-
function isRef(ref) {
|
|
111
|
-
return typeof ref === 'object' && ref !== null && '$ref' in ref && ref.$ref;
|
|
112
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { it, expect } from 'bun:test';
|
|
2
|
-
import { resolveOpenAPIPath } from './resolveOpenAPIPath';
|
|
3
|
-
const createFetcherForSchema = (schema) => {
|
|
4
|
-
return {
|
|
5
|
-
fetch: async (url) => {
|
|
6
|
-
return schema;
|
|
7
|
-
},
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
it('should resolve a simple path through objects', async () => {
|
|
11
|
-
const resolved = await resolveOpenAPIPath('https://test.com', ['a', 'b', 'c'], createFetcherForSchema({
|
|
12
|
-
a: {
|
|
13
|
-
b: {
|
|
14
|
-
c: 'hello',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
}));
|
|
18
|
-
expect(resolved).toBe('hello');
|
|
19
|
-
});
|
|
20
|
-
it('should return undefined if the last part of the path does not exists', async () => {
|
|
21
|
-
const resolved = await resolveOpenAPIPath('https://test.com', ['a', 'b', 'c'], createFetcherForSchema({
|
|
22
|
-
a: {
|
|
23
|
-
b: {
|
|
24
|
-
d: 'hello',
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
}));
|
|
28
|
-
expect(resolved).toBe(undefined);
|
|
29
|
-
});
|
|
30
|
-
it('should return undefined if a middle part of the path does not exists', async () => {
|
|
31
|
-
const resolved = await resolveOpenAPIPath('https://test.com', ['a', 'x', 'c'], createFetcherForSchema({
|
|
32
|
-
a: {
|
|
33
|
-
b: {
|
|
34
|
-
c: 'hello',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
}));
|
|
38
|
-
expect(resolved).toBe(undefined);
|
|
39
|
-
});
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { it, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { fetchOpenAPIOperation, parseOpenAPIV3 } from './fetchOpenAPIOperation';
|
|
4
|
-
import { OpenAPIFetcher } from './types';
|
|
5
|
-
|
|
6
|
-
const fetcher: OpenAPIFetcher = {
|
|
7
|
-
fetch: async (url) => {
|
|
8
|
-
const response = await fetch(url);
|
|
9
|
-
return parseOpenAPIV3(url, await response.text());
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
it('should resolve refs', async () => {
|
|
14
|
-
const resolved = await fetchOpenAPIOperation(
|
|
15
|
-
{
|
|
16
|
-
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
|
17
|
-
method: 'put',
|
|
18
|
-
path: '/pet',
|
|
19
|
-
},
|
|
20
|
-
fetcher,
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
expect(resolved).toMatchObject({
|
|
24
|
-
servers: [
|
|
25
|
-
{
|
|
26
|
-
url: '/api/v3',
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
operation: {
|
|
30
|
-
tags: ['pet'],
|
|
31
|
-
summary: 'Update an existing pet',
|
|
32
|
-
description: 'Update an existing pet by Id',
|
|
33
|
-
requestBody: {
|
|
34
|
-
content: {
|
|
35
|
-
'application/json': {
|
|
36
|
-
schema: {
|
|
37
|
-
type: 'object',
|
|
38
|
-
required: ['name', 'photoUrls'],
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should support yaml', async () => {
|
|
48
|
-
const resolved = await fetchOpenAPIOperation(
|
|
49
|
-
{
|
|
50
|
-
url: 'https://petstore3.swagger.io/api/v3/openapi.yaml',
|
|
51
|
-
method: 'put',
|
|
52
|
-
path: '/pet',
|
|
53
|
-
},
|
|
54
|
-
fetcher,
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
expect(resolved).toMatchObject({
|
|
58
|
-
servers: [
|
|
59
|
-
{
|
|
60
|
-
url: '/api/v3',
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
operation: {
|
|
64
|
-
tags: ['pet'],
|
|
65
|
-
summary: 'Update an existing pet',
|
|
66
|
-
description: 'Update an existing pet by Id',
|
|
67
|
-
requestBody: {
|
|
68
|
-
content: {
|
|
69
|
-
'application/json': {
|
|
70
|
-
schema: {
|
|
71
|
-
type: 'object',
|
|
72
|
-
required: ['name', 'photoUrls'],
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should resolve circular refs', async () => {
|
|
82
|
-
const resolved = await fetchOpenAPIOperation(
|
|
83
|
-
{
|
|
84
|
-
url: 'https://api.gitbook.com/openapi.json',
|
|
85
|
-
method: 'post',
|
|
86
|
-
path: '/search/ask',
|
|
87
|
-
},
|
|
88
|
-
fetcher,
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
expect(resolved).toMatchObject({
|
|
92
|
-
servers: [
|
|
93
|
-
{
|
|
94
|
-
url: '{host}/v1',
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
operation: {
|
|
98
|
-
operationId: 'askQuery',
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should resolve to null if the method is not supported', async () => {
|
|
104
|
-
const resolved = await fetchOpenAPIOperation(
|
|
105
|
-
{
|
|
106
|
-
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
|
107
|
-
method: 'dontexist',
|
|
108
|
-
path: '/pet',
|
|
109
|
-
},
|
|
110
|
-
fetcher,
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(resolved).toBe(null);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should parse Swagger 2.0', async () => {
|
|
117
|
-
const resolved = await fetchOpenAPIOperation(
|
|
118
|
-
{
|
|
119
|
-
url: 'https://petstore.swagger.io/v2/swagger.json',
|
|
120
|
-
method: 'put',
|
|
121
|
-
path: '/pet',
|
|
122
|
-
},
|
|
123
|
-
fetcher,
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
expect(resolved).toMatchObject({
|
|
127
|
-
servers: [
|
|
128
|
-
{
|
|
129
|
-
url: 'https://petstore.swagger.io/v2',
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
url: 'http://petstore.swagger.io/v2',
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
operation: {
|
|
136
|
-
tags: ['pet'],
|
|
137
|
-
summary: 'Update an existing pet',
|
|
138
|
-
description: '',
|
|
139
|
-
requestBody: {
|
|
140
|
-
content: {
|
|
141
|
-
'application/json': {
|
|
142
|
-
schema: {
|
|
143
|
-
type: 'object',
|
|
144
|
-
required: ['name', 'photoUrls'],
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should resolve a ref with whitespace', async () => {
|
|
154
|
-
const resolved = await fetchOpenAPIOperation(
|
|
155
|
-
{
|
|
156
|
-
url: ' https://petstore3.swagger.io/api/v3/openapi.json',
|
|
157
|
-
method: 'put',
|
|
158
|
-
path: '/pet',
|
|
159
|
-
},
|
|
160
|
-
fetcher,
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
expect(resolved).toMatchObject({
|
|
164
|
-
servers: [
|
|
165
|
-
{
|
|
166
|
-
url: '/api/v3',
|
|
167
|
-
},
|
|
168
|
-
],
|
|
169
|
-
operation: {
|
|
170
|
-
tags: ['pet'],
|
|
171
|
-
summary: 'Update an existing pet',
|
|
172
|
-
description: 'Update an existing pet by Id',
|
|
173
|
-
requestBody: {
|
|
174
|
-
content: {
|
|
175
|
-
'application/json': {
|
|
176
|
-
schema: {
|
|
177
|
-
type: 'object',
|
|
178
|
-
required: ['name', 'photoUrls'],
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
});
|