@gitbook/react-openapi 0.7.1 → 1.0.1
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 +49 -0
- package/dist/InteractiveSection.d.ts +4 -8
- package/dist/InteractiveSection.jsx +60 -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 +141 -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 +4 -5
- package/dist/OpenAPIRequestBody.jsx +22 -0
- package/dist/OpenAPIResponse.d.ts +4 -4
- package/dist/OpenAPIResponse.jsx +39 -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 +35 -0
- package/dist/OpenAPISchema.d.ts +11 -8
- package/dist/OpenAPISchema.jsx +285 -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 +26 -0
- package/dist/OpenAPITabs.jsx +103 -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/generateSchemaExample.d.ts +2 -2
- package/dist/generateSchemaExample.js +29 -102
- 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/types.d.ts +11 -12
- package/dist/useSyncedTabsGlobalState.d.ts +1 -0
- package/dist/useSyncedTabsGlobalState.js +16 -0
- package/dist/utils.d.ts +6 -2
- package/dist/utils.js +13 -6
- package/package.json +12 -10
- package/src/InteractiveSection.tsx +90 -86
- package/src/Markdown.tsx +2 -3
- package/src/OpenAPICodeSample.tsx +43 -31
- 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 +10 -17
- package/src/OpenAPIResponse.tsx +27 -45
- package/src/OpenAPIResponseExample.tsx +89 -31
- package/src/OpenAPIResponses.tsx +48 -17
- package/src/OpenAPISchema.test.ts +1 -1
- package/src/OpenAPISchema.tsx +129 -108
- 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 +58 -58
- package/src/OpenAPITabs.tsx +153 -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 +26 -153
- package/src/index.ts +3 -2
- package/src/resolveOpenAPIOperation.test.ts +177 -0
- package/src/resolveOpenAPIOperation.ts +164 -0
- package/src/stringifyOpenAPI.ts +6 -0
- package/src/types.ts +17 -10
- package/src/useSyncedTabsGlobalState.ts +23 -0
- package/src/utils.ts +14 -7
- 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.d.ts +0 -72
- package/dist/fetchOpenAPIOperation.js +0 -124
- 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/dist/tsconfig.tsbuildinfo +0 -1
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { OpenAPIV3 } from 'openapi-
|
|
2
|
-
import {
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
|
+
import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
|
|
3
3
|
|
|
4
4
|
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
|
5
5
|
|
|
@@ -11,154 +11,24 @@ export function generateSchemaExample(
|
|
|
11
11
|
options: {
|
|
12
12
|
onlyRequired?: boolean;
|
|
13
13
|
} = {},
|
|
14
|
-
ancestors: Set<OpenAPIV3.SchemaObject> = new Set(),
|
|
15
14
|
): JSONValue | undefined {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (schema.format === 'date-time') {
|
|
36
|
-
return new Date().toISOString();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (schema.format === 'date') {
|
|
40
|
-
return new Date().toISOString().split('T')[0];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (schema.format === 'email') {
|
|
44
|
-
return 'name@gmail.com';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (schema.format === 'hostname') {
|
|
48
|
-
return 'example.com';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (schema.format === 'ipv4') {
|
|
52
|
-
return '0.0.0.0';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (schema.format === 'ipv6') {
|
|
56
|
-
return '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (schema.format === 'uri') {
|
|
60
|
-
return 'https://example.com';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (schema.format === 'uuid') {
|
|
64
|
-
return '123e4567-e89b-12d3-a456-426614174000';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (schema.format === 'binary') {
|
|
68
|
-
return 'binary';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (schema.format === 'byte') {
|
|
72
|
-
return 'Ynl0ZXM=';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (schema.format === 'password') {
|
|
76
|
-
return 'password';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return 'text';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (schema.type === 'number' || schema.type === 'integer') {
|
|
83
|
-
return schema.default || 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (schema.type === 'boolean') {
|
|
87
|
-
return schema.default || false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (schema.type === 'array') {
|
|
91
|
-
if (schema.items) {
|
|
92
|
-
const exampleValue = generateSchemaExample(
|
|
93
|
-
noReference(schema.items),
|
|
94
|
-
options,
|
|
95
|
-
new Set(ancestors).add(schema),
|
|
96
|
-
);
|
|
97
|
-
if (exampleValue !== undefined) {
|
|
98
|
-
return [exampleValue];
|
|
99
|
-
}
|
|
100
|
-
return [];
|
|
101
|
-
}
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (schema.properties) {
|
|
106
|
-
const example: { [key: string]: JSONValue } = {};
|
|
107
|
-
const props = onlyRequired ? (schema.required ?? []) : Object.keys(schema.properties);
|
|
108
|
-
|
|
109
|
-
for (const key of props) {
|
|
110
|
-
const property = noReference(schema.properties[key]);
|
|
111
|
-
if (property && (onlyRequired || !property.deprecated)) {
|
|
112
|
-
const exampleValue = generateSchemaExample(
|
|
113
|
-
noReference(property),
|
|
114
|
-
options,
|
|
115
|
-
new Set(ancestors).add(schema),
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
if (exampleValue !== undefined) {
|
|
119
|
-
example[key] = exampleValue;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return example;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
127
|
-
return generateSchemaExample(
|
|
128
|
-
noReference(schema.oneOf[0]),
|
|
129
|
-
options,
|
|
130
|
-
new Set(ancestors).add(schema),
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
135
|
-
return generateSchemaExample(
|
|
136
|
-
noReference(schema.anyOf[0]),
|
|
137
|
-
options,
|
|
138
|
-
new Set(ancestors).add(schema),
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (schema.allOf && schema.allOf.length > 0) {
|
|
143
|
-
return schema.allOf.reduce(
|
|
144
|
-
(acc, curr) => {
|
|
145
|
-
const example = generateSchemaExample(
|
|
146
|
-
noReference(curr),
|
|
147
|
-
options,
|
|
148
|
-
new Set(ancestors).add(schema),
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
if (typeof example === 'object' && !Array.isArray(example) && example !== null) {
|
|
152
|
-
return { ...acc, ...example };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return acc;
|
|
156
|
-
},
|
|
157
|
-
{} as { [key: string]: JSONValue },
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return undefined;
|
|
15
|
+
return getExampleFromSchema(schema, {
|
|
16
|
+
emptyString: 'text',
|
|
17
|
+
omitEmptyAndOptionalProperties: options.onlyRequired,
|
|
18
|
+
variables: {
|
|
19
|
+
'date-time': new Date().toISOString(),
|
|
20
|
+
date: new Date().toISOString().split('T')[0],
|
|
21
|
+
email: 'name@gmail.com',
|
|
22
|
+
hostname: 'example.com',
|
|
23
|
+
ipv4: '0.0.0.0',
|
|
24
|
+
ipv6: '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
|
|
25
|
+
uri: 'https://example.com',
|
|
26
|
+
uuid: '123e4567-e89b-12d3-a456-426614174000',
|
|
27
|
+
binary: 'binary',
|
|
28
|
+
byte: 'Ynl0ZXM=',
|
|
29
|
+
password: 'password',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
162
32
|
}
|
|
163
33
|
|
|
164
34
|
/**
|
|
@@ -175,14 +45,17 @@ export function generateMediaTypeExample(
|
|
|
175
45
|
}
|
|
176
46
|
|
|
177
47
|
if (mediaType.examples) {
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
|
|
48
|
+
const key = Object.keys(mediaType.examples)[0];
|
|
49
|
+
if (key) {
|
|
50
|
+
const example = mediaType.examples[key];
|
|
51
|
+
if (example) {
|
|
52
|
+
return example.value;
|
|
53
|
+
}
|
|
181
54
|
}
|
|
182
55
|
}
|
|
183
56
|
|
|
184
57
|
if (mediaType.schema) {
|
|
185
|
-
return generateSchemaExample(
|
|
58
|
+
return generateSchemaExample(mediaType.schema, options);
|
|
186
59
|
}
|
|
187
60
|
|
|
188
61
|
return undefined;
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './resolveOpenAPIOperation';
|
|
2
2
|
export * from './OpenAPIOperation';
|
|
3
|
-
export
|
|
3
|
+
export * from './OpenAPIOperationContext';
|
|
4
|
+
export type { OpenAPIOperationData } from './types';
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { it, expect, describe } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { resolveOpenAPIOperation } from './resolveOpenAPIOperation';
|
|
4
|
+
import { parseOpenAPI, traverse } from '@gitbook/openapi-parser';
|
|
5
|
+
|
|
6
|
+
async function fetchFilesystem(url: string) {
|
|
7
|
+
const response = await fetch(url);
|
|
8
|
+
const text = await response.text();
|
|
9
|
+
const filesystem = await parseOpenAPI({ value: text, rootURL: url });
|
|
10
|
+
const transformedFs = await traverse(filesystem, async (node) => {
|
|
11
|
+
if ('description' in node && typeof node.description === 'string' && node.description) {
|
|
12
|
+
node['x-description-html'] = node.description;
|
|
13
|
+
}
|
|
14
|
+
return node;
|
|
15
|
+
});
|
|
16
|
+
return transformedFs;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('#resolveOpenAPIOperation', () => {
|
|
20
|
+
it('should resolve refs', async () => {
|
|
21
|
+
const filesystem = await fetchFilesystem(
|
|
22
|
+
'https://petstore3.swagger.io/api/v3/openapi.json',
|
|
23
|
+
);
|
|
24
|
+
const resolved = await resolveOpenAPIOperation(filesystem, { method: 'put', path: '/pet' });
|
|
25
|
+
|
|
26
|
+
expect(resolved).toMatchObject({
|
|
27
|
+
servers: [
|
|
28
|
+
{
|
|
29
|
+
url: '/api/v3',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
operation: {
|
|
33
|
+
tags: ['pet'],
|
|
34
|
+
summary: 'Update an existing pet',
|
|
35
|
+
description: 'Update an existing pet by Id',
|
|
36
|
+
requestBody: {
|
|
37
|
+
content: {
|
|
38
|
+
'application/json': {
|
|
39
|
+
schema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
required: ['name', 'photoUrls'],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should support yaml', async () => {
|
|
51
|
+
const filesystem = await fetchFilesystem(
|
|
52
|
+
'https://petstore3.swagger.io/api/v3/openapi.yaml',
|
|
53
|
+
);
|
|
54
|
+
const resolved = await resolveOpenAPIOperation(filesystem, { method: 'put', path: '/pet' });
|
|
55
|
+
|
|
56
|
+
expect(resolved).toMatchObject({
|
|
57
|
+
servers: [
|
|
58
|
+
{
|
|
59
|
+
url: '/api/v3',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
operation: {
|
|
63
|
+
tags: ['pet'],
|
|
64
|
+
summary: 'Update an existing pet',
|
|
65
|
+
description: 'Update an existing pet by Id',
|
|
66
|
+
requestBody: {
|
|
67
|
+
content: {
|
|
68
|
+
'application/json': {
|
|
69
|
+
schema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
required: ['name', 'photoUrls'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should resolve circular refs', async () => {
|
|
81
|
+
const filesystem = await fetchFilesystem('https://api.gitbook.com/openapi.json');
|
|
82
|
+
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
83
|
+
method: 'post',
|
|
84
|
+
path: '/search/ask',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(resolved).toMatchObject({
|
|
88
|
+
servers: [
|
|
89
|
+
{
|
|
90
|
+
url: '{host}/v1',
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
operation: {
|
|
94
|
+
operationId: 'askQuery',
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should resolve to null if the method is not supported', async () => {
|
|
100
|
+
const filesystem = await fetchFilesystem(
|
|
101
|
+
'https://petstore3.swagger.io/api/v3/openapi.json',
|
|
102
|
+
);
|
|
103
|
+
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
104
|
+
method: 'dontexist',
|
|
105
|
+
path: '/pet',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(resolved).toBe(null);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should parse Swagger 2.0', async () => {
|
|
112
|
+
const filesystem = await fetchFilesystem('https://petstore.swagger.io/v2/swagger.json');
|
|
113
|
+
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
114
|
+
method: 'put',
|
|
115
|
+
path: '/pet',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(resolved).toMatchObject({
|
|
119
|
+
servers: [
|
|
120
|
+
{
|
|
121
|
+
url: 'https://petstore.swagger.io/v2',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
url: 'http://petstore.swagger.io/v2',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
operation: {
|
|
128
|
+
tags: ['pet'],
|
|
129
|
+
summary: 'Update an existing pet',
|
|
130
|
+
description: '',
|
|
131
|
+
requestBody: {
|
|
132
|
+
content: {
|
|
133
|
+
'application/json': {
|
|
134
|
+
schema: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
required: ['name', 'photoUrls'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should resolve a ref with whitespace', async () => {
|
|
146
|
+
const filesystem = await fetchFilesystem(
|
|
147
|
+
' https://petstore3.swagger.io/api/v3/openapi.json',
|
|
148
|
+
);
|
|
149
|
+
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
150
|
+
method: 'put',
|
|
151
|
+
path: '/pet',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(resolved).toMatchObject({
|
|
155
|
+
servers: [
|
|
156
|
+
{
|
|
157
|
+
url: '/api/v3',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
operation: {
|
|
161
|
+
tags: ['pet'],
|
|
162
|
+
summary: 'Update an existing pet',
|
|
163
|
+
description: 'Update an existing pet by Id',
|
|
164
|
+
requestBody: {
|
|
165
|
+
content: {
|
|
166
|
+
'application/json': {
|
|
167
|
+
schema: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
required: ['name', 'photoUrls'],
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { toJSON, fromJSON } from 'flatted';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type OpenAPIV3xDocument,
|
|
5
|
+
type Filesystem,
|
|
6
|
+
type OpenAPIV3,
|
|
7
|
+
type OpenAPIV3_1,
|
|
8
|
+
dereference,
|
|
9
|
+
} from '@gitbook/openapi-parser';
|
|
10
|
+
import { OpenAPIOperationData } from './types';
|
|
11
|
+
import { checkIsReference } from './utils';
|
|
12
|
+
|
|
13
|
+
export { toJSON, fromJSON };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve an OpenAPI operation in a file and compile it to a more usable format.
|
|
17
|
+
*/
|
|
18
|
+
export async function resolveOpenAPIOperation(
|
|
19
|
+
filesystem: Filesystem<OpenAPIV3xDocument>,
|
|
20
|
+
operationDescriptor: {
|
|
21
|
+
path: string;
|
|
22
|
+
method: string;
|
|
23
|
+
},
|
|
24
|
+
): Promise<OpenAPIOperationData | null> {
|
|
25
|
+
const { path, method } = operationDescriptor;
|
|
26
|
+
const schema = await memoDereferenceFilesystem(filesystem);
|
|
27
|
+
let operation = getOperationByPathAndMethod(schema, path, method);
|
|
28
|
+
|
|
29
|
+
if (!operation) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Resolve common parameters
|
|
34
|
+
const commonParameters = getPathObjectParameter(schema, path);
|
|
35
|
+
if (commonParameters) {
|
|
36
|
+
operation = {
|
|
37
|
+
...operation,
|
|
38
|
+
parameters: [...commonParameters, ...(operation.parameters ?? [])],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const servers = 'servers' in schema ? (schema.servers ?? []) : [];
|
|
43
|
+
const security = flattenSecurities(operation.security ?? schema.security ?? []);
|
|
44
|
+
|
|
45
|
+
// Resolve securities
|
|
46
|
+
const securities: OpenAPIOperationData['securities'] = [];
|
|
47
|
+
for (const entry of security) {
|
|
48
|
+
const securityKey = Object.keys(entry)[0];
|
|
49
|
+
if (securityKey) {
|
|
50
|
+
const securityScheme = schema.components?.securitySchemes?.[securityKey];
|
|
51
|
+
if (securityScheme && !checkIsReference(securityScheme)) {
|
|
52
|
+
securities.push([securityKey, securityScheme]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
servers,
|
|
59
|
+
operation,
|
|
60
|
+
method,
|
|
61
|
+
path,
|
|
62
|
+
securities,
|
|
63
|
+
'x-codeSamples':
|
|
64
|
+
typeof schema['x-codeSamples'] === 'boolean' ? schema['x-codeSamples'] : undefined,
|
|
65
|
+
'x-hideTryItPanel':
|
|
66
|
+
typeof schema['x-hideTryItPanel'] === 'boolean'
|
|
67
|
+
? schema['x-hideTryItPanel']
|
|
68
|
+
: undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dereferenceCache = new WeakMap<Filesystem, Promise<OpenAPIV3xDocument>>();
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Memoized version of `dereferenceSchema`.
|
|
76
|
+
*/
|
|
77
|
+
function memoDereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
|
|
78
|
+
if (dereferenceCache.has(filesystem)) {
|
|
79
|
+
return dereferenceCache.get(filesystem) as Promise<OpenAPIV3xDocument>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const promise = dereferenceFilesystem(filesystem);
|
|
83
|
+
dereferenceCache.set(filesystem, promise);
|
|
84
|
+
return promise;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Dereference an OpenAPI schema.
|
|
89
|
+
*/
|
|
90
|
+
async function dereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
|
|
91
|
+
const result = await dereference(filesystem);
|
|
92
|
+
|
|
93
|
+
if (!result.schema) {
|
|
94
|
+
throw new Error('Failed to dereference OpenAPI document');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result.schema as OpenAPIV3xDocument;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get a path object from its path.
|
|
102
|
+
*/
|
|
103
|
+
function getPathObject(
|
|
104
|
+
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
|
105
|
+
path: string,
|
|
106
|
+
): OpenAPIV3.PathItemObject | OpenAPIV3_1.PathItemObject | null {
|
|
107
|
+
if (schema.paths?.[path]) {
|
|
108
|
+
return schema.paths[path];
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolve parameters from a path in an OpenAPI schema.
|
|
115
|
+
*/
|
|
116
|
+
function getPathObjectParameter(
|
|
117
|
+
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
|
118
|
+
path: string,
|
|
119
|
+
):
|
|
120
|
+
| (OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)[]
|
|
121
|
+
| (OpenAPIV3.ParameterObject | OpenAPIV3_1.ReferenceObject)[]
|
|
122
|
+
| null {
|
|
123
|
+
const pathObject = getPathObject(schema, path);
|
|
124
|
+
if (pathObject?.parameters) {
|
|
125
|
+
return pathObject.parameters;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get an operation by its path and method.
|
|
132
|
+
*/
|
|
133
|
+
function getOperationByPathAndMethod(
|
|
134
|
+
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
|
135
|
+
path: string,
|
|
136
|
+
method: string,
|
|
137
|
+
): OpenAPIV3.OperationObject | null {
|
|
138
|
+
// Types are buffy for OpenAPIV3_1.OperationObject, so we use v3
|
|
139
|
+
const pathObject = getPathObject(schema, path);
|
|
140
|
+
if (!pathObject) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const normalizedMethod = method.toLowerCase();
|
|
144
|
+
if (!pathObject[normalizedMethod]) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return pathObject[normalizedMethod];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Flatten security objects in case they are nested.
|
|
152
|
+
* @example [{bearerAuth:[], basicAuth:[]}] => [{ bearerAuth: [] }, { basicAuth: [] }]
|
|
153
|
+
*/
|
|
154
|
+
function flattenSecurities(security: OpenAPIV3.SecurityRequirementObject[]) {
|
|
155
|
+
if (!Array.isArray(security) || security.length === 0) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return security.flatMap((securityObject) => {
|
|
160
|
+
return Object.entries(securityObject).map(([authType, config]) => ({
|
|
161
|
+
[authType]: config,
|
|
162
|
+
}));
|
|
163
|
+
});
|
|
164
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import type {
|
|
2
|
+
OpenAPICustomOperationProperties,
|
|
3
|
+
OpenAPICustomSpecProperties,
|
|
4
|
+
OpenAPIV3,
|
|
5
|
+
} from '@gitbook/openapi-parser';
|
|
2
6
|
|
|
3
7
|
export interface OpenAPIContextProps extends OpenAPIClientContext {
|
|
4
8
|
CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
|
|
@@ -11,6 +15,7 @@ export interface OpenAPIClientContext {
|
|
|
11
15
|
icons: {
|
|
12
16
|
chevronDown: React.ReactNode;
|
|
13
17
|
chevronRight: React.ReactNode;
|
|
18
|
+
plus: React.ReactNode;
|
|
14
19
|
};
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -26,14 +31,16 @@ export interface OpenAPIClientContext {
|
|
|
26
31
|
id?: string;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
export interface
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
fetch: (url: string) => Promise<any>;
|
|
34
|
+
export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
|
35
|
+
path: string;
|
|
36
|
+
method: string;
|
|
34
37
|
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
/** Servers to be used for this operation */
|
|
39
|
+
servers: OpenAPIV3.ServerObject[];
|
|
40
|
+
|
|
41
|
+
/** Spec of the operation */
|
|
42
|
+
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
|
43
|
+
|
|
44
|
+
/** Securities that should be used for this operation */
|
|
45
|
+
securities: [string, OpenAPIV3.SecuritySchemeObject][];
|
|
39
46
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { create } from 'zustand';
|
|
4
|
+
|
|
5
|
+
interface SyncedTabsState<T> {
|
|
6
|
+
tabs: Map<string, T>;
|
|
7
|
+
setTabs: (updater: (tabs: Map<string, T>) => Map<string, T>) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const useSyncedTabsStore = create<SyncedTabsState<any>>()((set) => ({
|
|
11
|
+
tabs: new Map<string, any>(),
|
|
12
|
+
setTabs: (updater) =>
|
|
13
|
+
set((state) => ({
|
|
14
|
+
tabs: updater(new Map(state.tabs)), // Ensure a new Map is created for reactivity
|
|
15
|
+
})),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Selector for better performance - only re-renders when tabs change
|
|
19
|
+
export function useSyncedTabsGlobalState<T>() {
|
|
20
|
+
const tabs = useSyncedTabsStore((state) => state.tabs as Map<string, T>);
|
|
21
|
+
const setTabs = useSyncedTabsStore((state) => state.setTabs as SyncedTabsState<T>['setTabs']);
|
|
22
|
+
return [tabs, setTabs] as const;
|
|
23
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { OpenAPIV3 } from 'openapi-
|
|
1
|
+
import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
2
|
|
|
3
|
-
export function
|
|
4
|
-
|
|
5
|
-
throw new Error('Reference found');
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
return input;
|
|
3
|
+
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
|
4
|
+
return typeof input === 'object' && !!input && '$ref' in input;
|
|
9
5
|
}
|
|
10
6
|
|
|
11
7
|
export function createStateKey(key: string, scope?: string) {
|
|
12
8
|
return scope ? `${scope}_${key}` : key;
|
|
13
9
|
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the description of an object.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveDescription(object: AnyObject) {
|
|
15
|
+
return 'x-description-html' in object && typeof object['x-description-html'] === 'string'
|
|
16
|
+
? object['x-description-html']
|
|
17
|
+
: typeof object.description === 'string'
|
|
18
|
+
? object.description
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|