@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
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { OpenAPIV3 } from 'openapi-
|
|
1
|
+
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
2
|
import { noReference } from './utils';
|
|
3
|
+
import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
|
|
3
4
|
|
|
4
5
|
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
|
5
6
|
|
|
@@ -11,154 +12,24 @@ export function generateSchemaExample(
|
|
|
11
12
|
options: {
|
|
12
13
|
onlyRequired?: boolean;
|
|
13
14
|
} = {},
|
|
14
|
-
ancestors: Set<OpenAPIV3.SchemaObject> = new Set(),
|
|
15
15
|
): 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;
|
|
16
|
+
return getExampleFromSchema(schema, {
|
|
17
|
+
emptyString: 'text',
|
|
18
|
+
omitEmptyAndOptionalProperties: options.onlyRequired,
|
|
19
|
+
variables: {
|
|
20
|
+
'date-time': new Date().toISOString(),
|
|
21
|
+
date: new Date().toISOString().split('T')[0],
|
|
22
|
+
email: 'name@gmail.com',
|
|
23
|
+
hostname: 'example.com',
|
|
24
|
+
ipv4: '0.0.0.0',
|
|
25
|
+
ipv6: '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
|
|
26
|
+
uri: 'https://example.com',
|
|
27
|
+
uuid: '123e4567-e89b-12d3-a456-426614174000',
|
|
28
|
+
binary: 'binary',
|
|
29
|
+
byte: 'Ynl0ZXM=',
|
|
30
|
+
password: 'password',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
162
33
|
}
|
|
163
34
|
|
|
164
35
|
/**
|
|
@@ -175,9 +46,12 @@ export function generateMediaTypeExample(
|
|
|
175
46
|
}
|
|
176
47
|
|
|
177
48
|
if (mediaType.examples) {
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
|
|
49
|
+
const key = Object.keys(mediaType.examples)[0];
|
|
50
|
+
if (key) {
|
|
51
|
+
const example = mediaType.examples[key];
|
|
52
|
+
if (example) {
|
|
53
|
+
return noReference(example).value;
|
|
54
|
+
}
|
|
181
55
|
}
|
|
182
56
|
}
|
|
183
57
|
|
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,163 @@
|
|
|
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 { noReference } from './utils';
|
|
11
|
+
import { OpenAPIOperationData } from './types';
|
|
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) {
|
|
52
|
+
securities.push([securityKey, noReference(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
|
+
): OpenAPIV3.ParameterObject[] | OpenAPIV3_1.ParameterObject[] | null {
|
|
120
|
+
const pathObject = getPathObject(schema, path);
|
|
121
|
+
if (pathObject?.parameters) {
|
|
122
|
+
return pathObject.parameters.map(noReference) as
|
|
123
|
+
| OpenAPIV3.ParameterObject[]
|
|
124
|
+
| OpenAPIV3_1.ParameterObject[];
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get an operation by its path and method.
|
|
131
|
+
*/
|
|
132
|
+
function getOperationByPathAndMethod(
|
|
133
|
+
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
|
134
|
+
path: string,
|
|
135
|
+
method: string,
|
|
136
|
+
): OpenAPIV3.OperationObject | null {
|
|
137
|
+
// Types are buffy for OpenAPIV3_1.OperationObject, so we use v3
|
|
138
|
+
const pathObject = getPathObject(schema, path);
|
|
139
|
+
if (!pathObject) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const normalizedMethod = method.toLowerCase();
|
|
143
|
+
if (!pathObject[normalizedMethod]) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return pathObject[normalizedMethod];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Flatten security objects in case they are nested.
|
|
151
|
+
* @example [{bearerAuth:[], basicAuth:[]}] => [{ bearerAuth: [] }, { basicAuth: [] }]
|
|
152
|
+
*/
|
|
153
|
+
function flattenSecurities(security: OpenAPIV3.SecurityRequirementObject[]) {
|
|
154
|
+
if (!Array.isArray(security) || security.length === 0) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return security.flatMap((securityObject) => {
|
|
159
|
+
return Object.entries(securityObject).map(([authType, config]) => ({
|
|
160
|
+
[authType]: config,
|
|
161
|
+
}));
|
|
162
|
+
});
|
|
163
|
+
}
|
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
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import { OpenAPIV3 } from 'openapi-
|
|
1
|
+
import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
|
|
2
2
|
|
|
3
3
|
export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
|
|
4
|
-
if (
|
|
4
|
+
if (checkIsReference(input)) {
|
|
5
5
|
throw new Error('Reference found');
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
return input;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
|
12
|
+
return typeof input === 'object' && !!input && '$ref' in input;
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
export function createStateKey(key: string, scope?: string) {
|
|
12
16
|
return scope ? `${scope}_${key}` : key;
|
|
13
17
|
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the description of an object.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveDescription(object: AnyObject) {
|
|
23
|
+
return 'x-description-html' in object && typeof object['x-description-html'] === 'string'
|
|
24
|
+
? object['x-description-html']
|
|
25
|
+
: typeof object.description === 'string'
|
|
26
|
+
? object.description
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import classNames from 'classnames';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { atom, useRecoilState } from 'recoil';
|
|
5
|
-
const syncedTabsAtom = atom({
|
|
6
|
-
key: 'syncedTabState',
|
|
7
|
-
default: {},
|
|
8
|
-
});
|
|
9
|
-
/**
|
|
10
|
-
* To optimize rendering, most of the components are server-components,
|
|
11
|
-
* and the interactiveness is mainly handled by a few key components like this one.
|
|
12
|
-
*/
|
|
13
|
-
export function InteractiveSection(props) {
|
|
14
|
-
const { id, className, toggeable = false, defaultOpened = true, tabs = [], defaultTab = tabs[0]?.key, header, children, overlay, toggleOpenIcon = '▶', toggleCloseIcon = '▼', stateKey, } = props;
|
|
15
|
-
const [syncedTabs, setSyncedTabs] = useRecoilState(syncedTabsAtom);
|
|
16
|
-
const tabFromState = stateKey && stateKey in syncedTabs
|
|
17
|
-
? tabs.find((tab) => tab.key === syncedTabs[stateKey])
|
|
18
|
-
: undefined;
|
|
19
|
-
const [opened, setOpened] = React.useState(defaultOpened);
|
|
20
|
-
const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab);
|
|
21
|
-
const selectedTab = tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
|
|
22
|
-
return (React.createElement("div", { id: id, className: classNames('openapi-section', toggeable ? 'openapi-section-toggeable' : null, className, toggeable ? `${className}-${opened ? 'opened' : 'closed'}` : null) },
|
|
23
|
-
React.createElement("div", { onClick: () => {
|
|
24
|
-
if (toggeable) {
|
|
25
|
-
setOpened(!opened);
|
|
26
|
-
}
|
|
27
|
-
}, className: classNames('openapi-section-header', `${className}-header`) },
|
|
28
|
-
React.createElement("div", { className: classNames('openapi-section-header-content', `${className}-header-content`) }, header),
|
|
29
|
-
React.createElement("div", { className: classNames('openapi-section-header-controls', `${className}-header-controls`), onClick: (event) => {
|
|
30
|
-
event.stopPropagation();
|
|
31
|
-
} },
|
|
32
|
-
tabs.length ? (React.createElement("select", { className: classNames('openapi-section-select', 'openapi-select', `${className}-tabs-select`), value: selectedTab.key, onChange: (event) => {
|
|
33
|
-
setSelectedTab(event.target.value);
|
|
34
|
-
if (stateKey) {
|
|
35
|
-
setSyncedTabs((state) => ({
|
|
36
|
-
...state,
|
|
37
|
-
[stateKey]: event.target.value,
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
setOpened(true);
|
|
41
|
-
} }, tabs.map((tab) => (React.createElement("option", { key: tab.key, value: tab.key }, tab.label))))) : null,
|
|
42
|
-
(children || selectedTab?.body) && toggeable ? (React.createElement("button", { className: classNames('openapi-section-toggle', `${className}-toggle`), onClick: () => setOpened(!opened) }, opened ? toggleCloseIcon : toggleOpenIcon)) : null)),
|
|
43
|
-
(!toggeable || opened) && (children || selectedTab?.body) ? (React.createElement("div", { className: classNames('openapi-section-body', `${className}-body`) },
|
|
44
|
-
children,
|
|
45
|
-
selectedTab?.body)) : null,
|
|
46
|
-
overlay));
|
|
47
|
-
}
|
package/dist/Markdown.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import classNames from 'classnames';
|
|
3
|
-
export function Markdown(props) {
|
|
4
|
-
const { source, className } = props;
|
|
5
|
-
return (React.createElement("div", { className: classNames('openapi-markdown', className), dangerouslySetInnerHTML: { __html: source } }));
|
|
6
|
-
}
|