@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/InteractiveSection.d.ts +4 -8
  3. package/dist/InteractiveSection.jsx +60 -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 +141 -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 +4 -5
  19. package/dist/OpenAPIRequestBody.jsx +22 -0
  20. package/dist/OpenAPIResponse.d.ts +4 -4
  21. package/dist/OpenAPIResponse.jsx +39 -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 +35 -0
  26. package/dist/OpenAPISchema.d.ts +11 -8
  27. package/dist/OpenAPISchema.jsx +285 -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 +26 -0
  39. package/dist/OpenAPITabs.jsx +103 -0
  40. package/dist/ScalarApiButton.d.ts +3 -3
  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/generateSchemaExample.d.ts +2 -2
  45. package/dist/generateSchemaExample.js +29 -102
  46. package/dist/index.d.ts +3 -2
  47. package/dist/index.js +2 -1
  48. package/dist/resolveOpenAPIOperation.d.ts +11 -0
  49. package/dist/resolveOpenAPIOperation.js +194 -0
  50. package/dist/stringifyOpenAPI.d.ts +4 -0
  51. package/dist/stringifyOpenAPI.js +6 -0
  52. package/dist/tsconfig.build.tsbuildinfo +1 -0
  53. package/dist/types.d.ts +11 -12
  54. package/dist/useSyncedTabsGlobalState.d.ts +1 -0
  55. package/dist/useSyncedTabsGlobalState.js +16 -0
  56. package/dist/utils.d.ts +6 -2
  57. package/dist/utils.js +13 -6
  58. package/package.json +12 -10
  59. package/src/InteractiveSection.tsx +90 -86
  60. package/src/Markdown.tsx +2 -3
  61. package/src/OpenAPICodeSample.tsx +43 -31
  62. package/src/OpenAPIDisclosure.tsx +50 -0
  63. package/src/OpenAPIDisclosureGroup.tsx +136 -0
  64. package/src/OpenAPIOperation.tsx +36 -42
  65. package/src/OpenAPIOperationContext.tsx +45 -0
  66. package/src/OpenAPIPath.tsx +65 -0
  67. package/src/OpenAPIRequestBody.tsx +10 -17
  68. package/src/OpenAPIResponse.tsx +27 -45
  69. package/src/OpenAPIResponseExample.tsx +89 -31
  70. package/src/OpenAPIResponses.tsx +48 -17
  71. package/src/OpenAPISchema.test.ts +1 -1
  72. package/src/OpenAPISchema.tsx +129 -108
  73. package/src/OpenAPISchemaName.tsx +27 -0
  74. package/src/OpenAPISecurities.tsx +45 -24
  75. package/src/OpenAPIServerURL.tsx +17 -10
  76. package/src/OpenAPIServerURLVariable.tsx +2 -4
  77. package/src/OpenAPISpec.tsx +58 -58
  78. package/src/OpenAPITabs.tsx +153 -0
  79. package/src/ScalarApiButton.tsx +84 -7
  80. package/src/code-samples.test.ts +51 -0
  81. package/src/code-samples.ts +95 -31
  82. package/src/generateSchemaExample.ts +26 -153
  83. package/src/index.ts +3 -2
  84. package/src/resolveOpenAPIOperation.test.ts +177 -0
  85. package/src/resolveOpenAPIOperation.ts +164 -0
  86. package/src/stringifyOpenAPI.ts +6 -0
  87. package/src/types.ts +17 -10
  88. package/src/useSyncedTabsGlobalState.ts +23 -0
  89. package/src/utils.ts +14 -7
  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.d.ts +0 -72
  107. package/dist/fetchOpenAPIOperation.js +0 -124
  108. package/dist/fetchOpenAPIOperation.test.d.ts +0 -1
  109. package/dist/fetchOpenAPIOperation.test.js +0 -152
  110. package/dist/resolveOpenAPIPath.d.ts +0 -7
  111. package/dist/resolveOpenAPIPath.js +0 -112
  112. package/dist/resolveOpenAPIPath.test.d.ts +0 -1
  113. package/dist/resolveOpenAPIPath.test.js +0 -39
  114. package/dist/tsconfig.tsbuildinfo +0 -1
  115. package/src/fetchOpenAPIOperation.test.ts +0 -185
  116. package/src/fetchOpenAPIOperation.ts +0 -230
  117. package/src/resolveOpenAPIPath.test.ts +0 -60
  118. package/src/resolveOpenAPIPath.ts +0 -145
@@ -1,5 +1,5 @@
1
- import { OpenAPIV3 } from 'openapi-types';
2
- import { noReference } from './utils';
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
- 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' || 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 example = mediaType.examples[Object.keys(mediaType.examples)[0]];
179
- if (example) {
180
- return noReference(example).value;
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(noReference(mediaType.schema), options);
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 './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,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
+ }
@@ -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
  }
@@ -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-types';
1
+ import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
 
3
- export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
4
- if (typeof input === 'object' && !!input && '$ref' in input) {
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
+ }