@gitbook/react-openapi 0.7.0 → 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.
Files changed (115) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/InteractiveSection.d.ts +4 -6
  3. package/dist/InteractiveSection.jsx +96 -0
  4. package/dist/Markdown.d.ts +1 -2
  5. package/dist/Markdown.jsx +5 -0
  6. package/dist/OpenAPICodeSample.d.ts +2 -4
  7. package/dist/OpenAPICodeSample.jsx +143 -0
  8. package/dist/OpenAPIDisclosure.d.ts +12 -0
  9. package/dist/OpenAPIDisclosure.jsx +32 -0
  10. package/dist/OpenAPIDisclosureGroup.d.ts +19 -0
  11. package/dist/OpenAPIDisclosureGroup.jsx +81 -0
  12. package/dist/OpenAPIOperation.d.ts +2 -4
  13. package/dist/OpenAPIOperation.jsx +51 -0
  14. package/dist/OpenAPIOperationContext.d.ts +16 -0
  15. package/dist/OpenAPIOperationContext.jsx +26 -0
  16. package/dist/OpenAPIPath.d.ts +8 -0
  17. package/dist/OpenAPIPath.jsx +54 -0
  18. package/dist/OpenAPIRequestBody.d.ts +3 -4
  19. package/dist/OpenAPIRequestBody.jsx +19 -0
  20. package/dist/OpenAPIResponse.d.ts +4 -4
  21. package/dist/OpenAPIResponse.jsx +49 -0
  22. package/dist/OpenAPIResponseExample.d.ts +2 -4
  23. package/dist/OpenAPIResponseExample.jsx +108 -0
  24. package/dist/OpenAPIResponses.d.ts +3 -4
  25. package/dist/OpenAPIResponses.jsx +36 -0
  26. package/dist/OpenAPISchema.d.ts +11 -8
  27. package/dist/OpenAPISchema.jsx +295 -0
  28. package/dist/OpenAPISchemaName.d.ts +12 -0
  29. package/dist/OpenAPISchemaName.jsx +15 -0
  30. package/dist/OpenAPISecurities.d.ts +2 -4
  31. package/dist/OpenAPISecurities.jsx +55 -0
  32. package/dist/OpenAPIServerURL.d.ts +2 -3
  33. package/dist/OpenAPIServerURL.jsx +67 -0
  34. package/dist/OpenAPIServerURLVariable.d.ts +2 -3
  35. package/dist/OpenAPIServerURLVariable.jsx +8 -0
  36. package/dist/OpenAPISpec.d.ts +3 -4
  37. package/dist/OpenAPISpec.jsx +91 -0
  38. package/dist/OpenAPITabs.d.ts +25 -0
  39. package/dist/OpenAPITabs.jsx +67 -0
  40. package/dist/ScalarApiButton.d.ts +5 -2
  41. package/dist/ScalarApiButton.jsx +51 -0
  42. package/dist/code-samples.d.ts +4 -0
  43. package/dist/code-samples.js +103 -38
  44. package/dist/fetchOpenAPIOperation.d.ts +9 -54
  45. package/dist/fetchOpenAPIOperation.js +178 -107
  46. package/dist/generateSchemaExample.d.ts +2 -2
  47. package/dist/generateSchemaExample.js +28 -100
  48. package/dist/index.d.ts +3 -2
  49. package/dist/index.js +2 -1
  50. package/dist/resolveOpenAPIOperation.d.ts +11 -0
  51. package/dist/resolveOpenAPIOperation.js +194 -0
  52. package/dist/stringifyOpenAPI.d.ts +4 -0
  53. package/dist/stringifyOpenAPI.js +6 -0
  54. package/dist/tsconfig.build.tsbuildinfo +1 -0
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/dist/types.d.ts +11 -12
  57. package/dist/utils.d.ts +6 -1
  58. package/dist/utils.js +15 -2
  59. package/package.json +12 -11
  60. package/src/InteractiveSection.tsx +119 -78
  61. package/src/Markdown.tsx +2 -3
  62. package/src/OpenAPICodeSample.tsx +35 -21
  63. package/src/OpenAPIDisclosure.tsx +50 -0
  64. package/src/OpenAPIDisclosureGroup.tsx +136 -0
  65. package/src/OpenAPIOperation.tsx +36 -42
  66. package/src/OpenAPIOperationContext.tsx +45 -0
  67. package/src/OpenAPIPath.tsx +65 -0
  68. package/src/OpenAPIRequestBody.tsx +3 -14
  69. package/src/OpenAPIResponse.tsx +39 -43
  70. package/src/OpenAPIResponseExample.tsx +89 -31
  71. package/src/OpenAPIResponses.tsx +51 -15
  72. package/src/OpenAPISchema.test.ts +1 -1
  73. package/src/OpenAPISchema.tsx +124 -92
  74. package/src/OpenAPISchemaName.tsx +27 -0
  75. package/src/OpenAPISecurities.tsx +45 -24
  76. package/src/OpenAPIServerURL.tsx +17 -10
  77. package/src/OpenAPIServerURLVariable.tsx +2 -4
  78. package/src/OpenAPISpec.tsx +56 -53
  79. package/src/OpenAPITabs.tsx +113 -0
  80. package/src/ScalarApiButton.tsx +86 -6
  81. package/src/code-samples.test.ts +51 -0
  82. package/src/code-samples.ts +95 -31
  83. package/src/generateSchemaExample.ts +25 -151
  84. package/src/index.ts +3 -2
  85. package/src/resolveOpenAPIOperation.test.ts +177 -0
  86. package/src/resolveOpenAPIOperation.ts +163 -0
  87. package/src/stringifyOpenAPI.ts +6 -0
  88. package/src/types.ts +17 -10
  89. package/src/utils.ts +17 -2
  90. package/dist/InteractiveSection.js +0 -47
  91. package/dist/Markdown.js +0 -6
  92. package/dist/OpenAPICodeSample.js +0 -110
  93. package/dist/OpenAPIOperation.js +0 -38
  94. package/dist/OpenAPIRequestBody.js +0 -18
  95. package/dist/OpenAPIResponse.js +0 -32
  96. package/dist/OpenAPIResponseExample.js +0 -54
  97. package/dist/OpenAPIResponses.js +0 -18
  98. package/dist/OpenAPISchema.js +0 -235
  99. package/dist/OpenAPISchema.test.d.ts +0 -1
  100. package/dist/OpenAPISchema.test.js +0 -91
  101. package/dist/OpenAPISecurities.js +0 -42
  102. package/dist/OpenAPIServerURL.js +0 -51
  103. package/dist/OpenAPIServerURLVariable.js +0 -10
  104. package/dist/OpenAPISpec.js +0 -70
  105. package/dist/ScalarApiButton.js +0 -14
  106. package/dist/fetchOpenAPIOperation.test.d.ts +0 -1
  107. package/dist/fetchOpenAPIOperation.test.js +0 -152
  108. package/dist/resolveOpenAPIPath.d.ts +0 -7
  109. package/dist/resolveOpenAPIPath.js +0 -112
  110. package/dist/resolveOpenAPIPath.test.d.ts +0 -1
  111. package/dist/resolveOpenAPIPath.test.js +0 -39
  112. package/src/fetchOpenAPIOperation.test.ts +0 -185
  113. package/src/fetchOpenAPIOperation.ts +0 -230
  114. package/src/resolveOpenAPIPath.test.ts +0 -60
  115. package/src/resolveOpenAPIPath.ts +0 -145
@@ -1,5 +1,6 @@
1
- import { OpenAPIV3 } from 'openapi-types';
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
- const { onlyRequired = false } = options;
17
-
18
- if (ancestors.has(schema)) {
19
- return undefined;
20
- }
21
-
22
- if (typeof schema.example !== 'undefined') {
23
- return schema.example;
24
- }
25
-
26
- if (schema.enum && schema.enum.length > 0) {
27
- return schema.enum[0];
28
- }
29
-
30
- if (schema.type === 'string') {
31
- if (schema.default) {
32
- return schema.default;
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') {
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 example = mediaType.examples[Object.keys(mediaType.examples)[0]];
179
- if (example) {
180
- return noReference(example).value;
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 './fetchOpenAPIOperation';
1
+ export * from './resolveOpenAPIOperation';
2
2
  export * from './OpenAPIOperation';
3
- export type { OpenAPIFetcher } from './types';
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
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Stringify an OpenAPI object. Same API as JSON.stringify.
3
+ */
4
+ export function stringifyOpenAPI(body: unknown, transformer?: null, indent?: number): string {
5
+ return JSON.stringify(body, transformer, indent);
6
+ }
package/src/types.ts CHANGED
@@ -1,4 +1,8 @@
1
- export type IconComponent = React.ComponentType<{ className?: string }>;
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 OpenAPIFetcher {
30
- /**
31
- * Fetch an OpenAPI file by its URL. It should return a fully parsed OpenAPI v3 document.
32
- */
33
- fetch: (url: string) => Promise<any>;
34
+ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
35
+ path: string;
36
+ method: string;
34
37
 
35
- /**
36
- * Parse markdown to the react element to render.
37
- */
38
- parseMarkdown?: (input: string) => Promise<string>;
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-types';
1
+ import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
 
3
3
  export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
4
- if (typeof input === 'object' && !!input && '$ref' in input) {
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
- }