Skip to content

Commit 636d324

Browse files
committed
Improve OpenAPI parsing errors
We make the error more explicit, saying that we didn't find any version.
1 parent 3500de9 commit 636d324

File tree

5 files changed

+48
-30
lines changed

5 files changed

+48
-30
lines changed

.changeset/tough-beers-own.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@gitbook/openapi-parser": patch
3+
"gitbook": patch
4+
---
5+
6+
Improve OpenAPI parsing errors

packages/gitbook/src/lib/openapi/fetch.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,28 @@ export async function fetchOpenAPIFilesystem(
3636
return { filesystem: null, specUrl: null };
3737
}
3838

39-
const filesystem = await (() => {
39+
const result = await (() => {
40+
// If the reference is a new OpenAPI reference, we return it.
4041
if (ref.kind === 'openapi') {
4142
assert(resolved.openAPIFilesystem);
4243
return resolved.openAPIFilesystem;
4344
}
45+
// For legacy blocks ("swagger"), we need to fetch the file system.
4446
return fetchFilesystem(resolved.href, context.space.id);
4547
})();
4648

47-
if ('error' in filesystem) {
48-
throw new OpenAPIParseError(filesystem.error.message, { code: filesystem.error.code });
49+
if ('error' in result) {
50+
throw new OpenAPIParseError(result.error.message, { code: result.error.code });
4951
}
5052

51-
return {
52-
filesystem,
53-
specUrl: resolved.href,
54-
};
53+
return { filesystem: result, specUrl: resolved.href };
5554
}
5655

57-
const fetchFilesystem = async (
56+
/**
57+
* Fetch the filesystem from the URL.
58+
* It's used for legacy "swagger" blocks.
59+
*/
60+
async function fetchFilesystem(
5861
url: string,
5962
spaceId: string
6063
): Promise<
@@ -65,11 +68,11 @@ const fetchFilesystem = async (
6568
message: string;
6669
};
6770
}
68-
> => {
71+
> {
6972
'use cache';
7073
try {
7174
cacheTag(getCacheTag({ tag: 'space', space: spaceId }));
72-
return await fetchFilesystemUncached(url);
75+
return await fetchFilesystemNoCache(url);
7376
} catch (error) {
7477
// To avoid hammering the file with requests, we cache the error for around a minute.
7578
cacheLife('minutes');
@@ -86,21 +89,16 @@ const fetchFilesystem = async (
8689
console.error('Unknown error while fetching OpenAPI file:', error);
8790
return { error: { code: 'invalid' as const, message: 'Unknown error' } };
8891
}
89-
};
92+
}
9093

91-
async function fetchFilesystemUncached(
92-
url: string,
93-
options?: {
94-
signal?: AbortSignal;
95-
}
96-
) {
94+
async function fetchFilesystemNoCache(url: string) {
95+
console.log(url);
9796
// Wrap the raw string to prevent invalid URLs from being passed to fetch.
9897
// This can happen if the URL has whitespace, which is currently handled differently by Cloudflare's implementation of fetch:
9998
// https://github.com/cloudflare/workerd/issues/1957
10099
const response = await fetch(new URL(url), {
101100
...noCacheFetchOptions,
102101
cache: 'no-store',
103-
signal: options?.signal,
104102
});
105103

106104
if (!response.ok) {

packages/gitbook/src/lib/openapi/resolveOpenAPIOperationBlock.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ import type {
99

1010
type ResolveOpenAPIOperationBlockResult = ResolveOpenAPIBlockResult<OpenAPIOperationData>;
1111

12-
const weakmap = new WeakMap<
13-
AnyOpenAPIOperationsBlock,
14-
Promise<ResolveOpenAPIOperationBlockResult>
15-
>();
12+
const cache = new WeakMap<AnyOpenAPIOperationsBlock, Promise<ResolveOpenAPIOperationBlockResult>>();
1613

1714
/**
1815
* Cache the result of resolving an OpenAPI block.
@@ -21,22 +18,24 @@ const weakmap = new WeakMap<
2118
export function resolveOpenAPIOperationBlock(
2219
args: ResolveOpenAPIBlockArgs<AnyOpenAPIOperationsBlock>
2320
): Promise<ResolveOpenAPIOperationBlockResult> {
24-
if (weakmap.has(args.block)) {
25-
return weakmap.get(args.block)!;
21+
const inCache = cache.get(args.block);
22+
if (inCache) {
23+
return inCache;
2624
}
2725

28-
const result = baseResolveOpenAPIOperationBlock(args);
29-
weakmap.set(args.block, result);
30-
return result;
26+
const promise = resolveOpenAPIOperationBlockNoCache(args);
27+
cache.set(args.block, promise);
28+
return promise;
3129
}
3230

3331
/**
3432
* Resolve OpenAPI operation block.
3533
*/
36-
async function baseResolveOpenAPIOperationBlock(
34+
async function resolveOpenAPIOperationBlockNoCache(
3735
args: ResolveOpenAPIBlockArgs<AnyOpenAPIOperationsBlock>
3836
): Promise<ResolveOpenAPIOperationBlockResult> {
3937
const { context, block } = args;
38+
4039
if (!block.data.path || !block.data.method) {
4140
return { data: null, specUrl: null };
4241
}

packages/openapi-parser/src/parse.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ describe('#parseOpenAPI', () => {
3939
});
4040
} catch (error) {
4141
if (error instanceof OpenAPIParseError) {
42-
expect(error.message).toContain('Invalid OpenAPI document');
42+
expect(error.message).toContain(
43+
'Can’t find supported Swagger/OpenAPI version in the provided document, version must be a string.'
44+
);
4345
}
4446
}
4547
});

packages/openapi-parser/src/v3.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ export async function parseOpenAPIV3(input: ParseOpenAPIInput): Promise<ParseOpe
1212
const { value, rootURL, options = {} } = input;
1313
const result = await validate(value);
1414

15+
// If there is no version, we consider it invalid instantely.
16+
if (!result.version) {
17+
throw new OpenAPIParseError(
18+
'Can’t find supported Swagger/OpenAPI version in the provided document, version must be a string.',
19+
{
20+
code: 'invalid',
21+
rootURL,
22+
errors: result.errors,
23+
}
24+
);
25+
}
26+
27+
// If the version is 2.0, we throw an error to trigger the upgrade.
1528
if (result.version === '2.0') {
1629
throw new OpenAPIParseError('Only OpenAPI v3 is supported', {
1730
code: 'parse-v2-in-v3',
@@ -21,7 +34,7 @@ export async function parseOpenAPIV3(input: ParseOpenAPIInput): Promise<ParseOpe
2134

2235
// We don't rely on `result.invalid` because it's too strict.
2336
// If we succeed in parsing a schema, then we consider it valid.
24-
if (!result.specification || !result.version) {
37+
if (!result.specification) {
2538
throw new OpenAPIParseError('Invalid OpenAPI document', {
2639
code: 'invalid',
2740
rootURL,

0 commit comments

Comments
 (0)