@gitbook/react-openapi 1.0.5 → 1.1.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 +21 -0
- package/dist/OpenAPIDisclosure.d.ts +5 -9
- package/dist/OpenAPIDisclosure.jsx +24 -27
- package/dist/OpenAPIDisclosureGroup.d.ts +1 -1
- package/dist/OpenAPIDisclosureGroup.jsx +5 -5
- package/dist/OpenAPIPath.jsx +5 -1
- package/dist/OpenAPISchema.d.ts +3 -26
- package/dist/OpenAPISchema.jsx +81 -132
- package/dist/ScalarApiButton.d.ts +3 -2
- package/dist/ScalarApiButton.jsx +22 -18
- package/dist/dereference.d.ts +5 -0
- package/dist/dereference.js +68 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/models/OpenAPIModels.d.ts +9 -0
- package/dist/models/OpenAPIModels.jsx +62 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.js +2 -0
- package/dist/models/resolveOpenAPIModels.d.ts +7 -0
- package/dist/models/resolveOpenAPIModels.js +73 -0
- package/dist/resolveOpenAPIOperation.d.ts +2 -2
- package/dist/resolveOpenAPIOperation.js +3 -34
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +43 -4
- package/package.json +3 -3
- package/src/OpenAPIDisclosure.tsx +34 -42
- package/src/OpenAPIDisclosureGroup.tsx +2 -2
- package/src/OpenAPIPath.tsx +7 -1
- package/src/OpenAPISchema.test.ts +26 -35
- package/src/OpenAPISchema.tsx +137 -226
- package/src/ScalarApiButton.tsx +26 -28
- package/src/dereference.ts +29 -0
- package/src/index.ts +3 -2
- package/src/models/OpenAPIModels.tsx +89 -0
- package/src/models/index.ts +2 -0
- package/src/models/resolveOpenAPIModels.ts +35 -0
- package/src/resolveOpenAPIOperation.ts +8 -36
- package/src/types.ts +10 -0
- package/src/utils.ts +53 -5
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './models';
|
|
2
2
|
export * from './OpenAPIOperation';
|
|
3
3
|
export * from './OpenAPIOperationContext';
|
|
4
|
-
export
|
|
4
|
+
export * from './resolveOpenAPIOperation';
|
|
5
|
+
export type { OpenAPIModelsData, OpenAPIOperationData } from './types';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { OpenAPIDisclosureGroup } from '../OpenAPIDisclosureGroup';
|
|
3
|
+
import { OpenAPIRootSchema } from '../OpenAPISchema';
|
|
4
|
+
import { Section, SectionBody } from '../StaticSection';
|
|
5
|
+
import type { OpenAPIClientContext, OpenAPIContextProps, OpenAPIModelsData } from '../types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Display OpenAPI Models.
|
|
9
|
+
*/
|
|
10
|
+
export function OpenAPIModels(props: {
|
|
11
|
+
className?: string;
|
|
12
|
+
data: OpenAPIModelsData;
|
|
13
|
+
context: OpenAPIContextProps;
|
|
14
|
+
}) {
|
|
15
|
+
const { className, data, context } = props;
|
|
16
|
+
const { models } = data;
|
|
17
|
+
|
|
18
|
+
const clientContext: OpenAPIClientContext = {
|
|
19
|
+
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
|
20
|
+
icons: context.icons,
|
|
21
|
+
blockKey: context.blockKey,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (!models.length) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={clsx('openapi-models', className)}>
|
|
30
|
+
<OpenAPIRootModelsSchema models={models} context={clientContext} />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Root schema for OpenAPI models.
|
|
37
|
+
* It displays a single model or a disclosure group for multiple models.
|
|
38
|
+
*/
|
|
39
|
+
function OpenAPIRootModelsSchema(props: {
|
|
40
|
+
models: OpenAPIModelsData['models'];
|
|
41
|
+
context: OpenAPIClientContext;
|
|
42
|
+
}) {
|
|
43
|
+
const { models, context } = props;
|
|
44
|
+
|
|
45
|
+
// If there is only one model, we show it directly.
|
|
46
|
+
if (models.length === 1) {
|
|
47
|
+
const schema = models?.[0]?.schema;
|
|
48
|
+
|
|
49
|
+
if (!schema) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Section>
|
|
55
|
+
<SectionBody>
|
|
56
|
+
<OpenAPIRootSchema schema={schema} context={context} />
|
|
57
|
+
</SectionBody>
|
|
58
|
+
</Section>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If there are multiple models, we use a disclosure group to show them all.
|
|
63
|
+
return (
|
|
64
|
+
<OpenAPIDisclosureGroup
|
|
65
|
+
allowsMultipleExpanded
|
|
66
|
+
icon={context.icons.chevronRight}
|
|
67
|
+
groups={models.map(({ name, schema }) => ({
|
|
68
|
+
id: name,
|
|
69
|
+
label: (
|
|
70
|
+
<div className="openapi-response-tab-content" key={`model-${name}`}>
|
|
71
|
+
<span className="openapi-response-statuscode">{name}</span>
|
|
72
|
+
</div>
|
|
73
|
+
),
|
|
74
|
+
tabs: [
|
|
75
|
+
{
|
|
76
|
+
id: 'model',
|
|
77
|
+
body: (
|
|
78
|
+
<Section className="openapi-section-models">
|
|
79
|
+
<SectionBody>
|
|
80
|
+
<OpenAPIRootSchema schema={schema} context={context} />
|
|
81
|
+
</SectionBody>
|
|
82
|
+
</Section>
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
}))}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Filesystem,
|
|
3
|
+
type OpenAPIV3,
|
|
4
|
+
type OpenAPIV3_1,
|
|
5
|
+
type OpenAPIV3xDocument,
|
|
6
|
+
shouldIgnoreEntity,
|
|
7
|
+
} from '@gitbook/openapi-parser';
|
|
8
|
+
import { dereferenceFilesystem } from '../dereference';
|
|
9
|
+
import type { OpenAPIModel, OpenAPIModelsData } from '../types';
|
|
10
|
+
|
|
11
|
+
//!!TODO: We should return only the models that are used in the block. Still a WIP awaiting future work.
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve an OpenAPI models from a file and compile it to a more usable format.
|
|
15
|
+
* Models are extracted from the OpenAPI components.schemas
|
|
16
|
+
*/
|
|
17
|
+
export async function resolveOpenAPIModels(
|
|
18
|
+
filesystem: Filesystem<OpenAPIV3xDocument>
|
|
19
|
+
): Promise<OpenAPIModelsData | null> {
|
|
20
|
+
const schema = await dereferenceFilesystem(filesystem);
|
|
21
|
+
|
|
22
|
+
const models = getOpenAPIComponents(schema);
|
|
23
|
+
|
|
24
|
+
return { models };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get OpenAPI components.schemas that are not ignored.
|
|
29
|
+
*/
|
|
30
|
+
function getOpenAPIComponents(schema: OpenAPIV3.Document | OpenAPIV3_1.Document): OpenAPIModel[] {
|
|
31
|
+
const schemas = schema.components?.schemas ?? {};
|
|
32
|
+
return Object.entries(schemas)
|
|
33
|
+
.filter(([, schema]) => !shouldIgnoreEntity(schema))
|
|
34
|
+
.map(([key, schema]) => ({ name: key, schema }));
|
|
35
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { fromJSON, toJSON } from 'flatted';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
dereference,
|
|
3
|
+
import type {
|
|
4
|
+
Filesystem,
|
|
5
|
+
OpenAPIV3,
|
|
6
|
+
OpenAPIV3_1,
|
|
7
|
+
OpenAPIV3xDocument,
|
|
9
8
|
} from '@gitbook/openapi-parser';
|
|
9
|
+
import { dereferenceFilesystem } from './dereference';
|
|
10
10
|
import type { OpenAPIOperationData } from './types';
|
|
11
11
|
import { checkIsReference } from './utils';
|
|
12
12
|
|
|
13
|
-
export {
|
|
13
|
+
export { fromJSON, toJSON };
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Resolve an OpenAPI operation in a file and compile it to a more usable format.
|
|
@@ -23,7 +23,7 @@ export async function resolveOpenAPIOperation(
|
|
|
23
23
|
}
|
|
24
24
|
): Promise<OpenAPIOperationData | null> {
|
|
25
25
|
const { path, method } = operationDescriptor;
|
|
26
|
-
const schema = await
|
|
26
|
+
const schema = await dereferenceFilesystem(filesystem);
|
|
27
27
|
let operation = getOperationByPathAndMethod(schema, path, method);
|
|
28
28
|
|
|
29
29
|
if (!operation) {
|
|
@@ -69,34 +69,6 @@ export async function resolveOpenAPIOperation(
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
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
72
|
/**
|
|
101
73
|
* Get a path object from its path.
|
|
102
74
|
*/
|
package/src/types.ts
CHANGED
|
@@ -55,3 +55,13 @@ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
|
|
55
55
|
/** Securities that should be used for this operation */
|
|
56
56
|
securities: [string, OpenAPIV3.SecuritySchemeObject][];
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
export type OpenAPIModel = {
|
|
60
|
+
name: string;
|
|
61
|
+
schema: OpenAPIV3.SchemaObject;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export interface OpenAPIModelsData {
|
|
65
|
+
/** Components schemas to be used for models */
|
|
66
|
+
models: OpenAPIModel[];
|
|
67
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
|
2
|
+
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
2
3
|
|
|
3
4
|
export function checkIsReference(
|
|
4
5
|
input: unknown
|
|
@@ -10,11 +11,19 @@ export function createStateKey(key: string, scope?: string) {
|
|
|
10
11
|
return scope ? `${scope}_${key}` : key;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Check if an object has a description. Either at the root level or in items.
|
|
16
|
+
*/
|
|
17
|
+
function hasDescription(object: AnyObject) {
|
|
18
|
+
return 'description' in object || 'x-gitbook-description-html' in object;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* Resolve the description of an object.
|
|
15
23
|
*/
|
|
16
24
|
export function resolveDescription(object: OpenAPIV3.SchemaObject | AnyObject) {
|
|
17
|
-
|
|
25
|
+
// If the object has items and has a description, we resolve the description from items
|
|
26
|
+
if ('items' in object && typeof object.items === 'object' && hasDescription(object.items)) {
|
|
18
27
|
return resolveDescription(object.items);
|
|
19
28
|
}
|
|
20
29
|
|
|
@@ -42,17 +51,25 @@ export function extractDescriptions(object: AnyObject) {
|
|
|
42
51
|
/**
|
|
43
52
|
* Resolve the first example from an object.
|
|
44
53
|
*/
|
|
45
|
-
export function resolveFirstExample(object: AnyObject) {
|
|
54
|
+
export function resolveFirstExample(object: AnyObject): string | undefined {
|
|
46
55
|
if ('examples' in object && typeof object.examples === 'object' && object.examples) {
|
|
47
56
|
const keys = Object.keys(object.examples);
|
|
48
57
|
const firstKey = keys[0];
|
|
49
58
|
if (firstKey && object.examples[firstKey]) {
|
|
50
|
-
return object.examples[firstKey];
|
|
59
|
+
return formatExample(object.examples[firstKey]);
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
|
|
63
|
+
// Resolve top level example first
|
|
64
|
+
if (shouldDisplayExample(object)) {
|
|
65
|
+
return formatExample(object.example);
|
|
55
66
|
}
|
|
67
|
+
|
|
68
|
+
// Resolve example from items if it exists
|
|
69
|
+
if (object.items && typeof object.items === 'object') {
|
|
70
|
+
return formatExample(object.items.example);
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
return undefined;
|
|
57
74
|
}
|
|
58
75
|
|
|
@@ -98,3 +115,34 @@ export function parameterToProperty(
|
|
|
98
115
|
required: parameter.required,
|
|
99
116
|
};
|
|
100
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format the example of a schema.
|
|
121
|
+
*/
|
|
122
|
+
function formatExample(example: unknown): string {
|
|
123
|
+
if (typeof example === 'string') {
|
|
124
|
+
return example
|
|
125
|
+
.replace(/\n/g, ' ') // Replace newlines with spaces
|
|
126
|
+
.replace(/\s+/g, ' ') // Collapse multiple spaces/newlines into a single space
|
|
127
|
+
.replace(/([\{\}:,])\s+/g, '$1 ') // Ensure a space after {, }, :, and ,
|
|
128
|
+
.replace(/\s+([\{\}:,])/g, ' $1') // Ensure a space before {, }, :, and ,
|
|
129
|
+
.trim();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return stringifyOpenAPI(example);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if an example should be displayed.
|
|
137
|
+
*/
|
|
138
|
+
function shouldDisplayExample(schema: OpenAPIV3.SchemaObject): boolean {
|
|
139
|
+
return (
|
|
140
|
+
(typeof schema.example === 'string' && !!schema.example) ||
|
|
141
|
+
typeof schema.example === 'number' ||
|
|
142
|
+
typeof schema.example === 'boolean' ||
|
|
143
|
+
(Array.isArray(schema.example) && schema.example.length > 0) ||
|
|
144
|
+
(typeof schema.example === 'object' &&
|
|
145
|
+
schema.example !== null &&
|
|
146
|
+
Object.keys(schema.example).length > 0)
|
|
147
|
+
);
|
|
148
|
+
}
|