diff --git a/.changeset/tough-beers-own.md b/.changeset/tough-beers-own.md new file mode 100644 index 0000000000..3d797027bf --- /dev/null +++ b/.changeset/tough-beers-own.md @@ -0,0 +1,6 @@ +--- +"@gitbook/openapi-parser": patch +"gitbook": patch +--- + +Improve OpenAPI parsing errors diff --git a/packages/gitbook/src/lib/openapi/fetch.ts b/packages/gitbook/src/lib/openapi/fetch.ts index a5dabcb5b1..366584e076 100644 --- a/packages/gitbook/src/lib/openapi/fetch.ts +++ b/packages/gitbook/src/lib/openapi/fetch.ts @@ -36,25 +36,28 @@ export async function fetchOpenAPIFilesystem( return { filesystem: null, specUrl: null }; } - const filesystem = await (() => { + const result = await (() => { + // If the reference is a new OpenAPI reference, we return it. if (ref.kind === 'openapi') { assert(resolved.openAPIFilesystem); return resolved.openAPIFilesystem; } + // For legacy blocks ("swagger"), we need to fetch the file system. return fetchFilesystem(resolved.href, context.space.id); })(); - if ('error' in filesystem) { - throw new OpenAPIParseError(filesystem.error.message, { code: filesystem.error.code }); + if ('error' in result) { + throw new OpenAPIParseError(result.error.message, { code: result.error.code }); } - return { - filesystem, - specUrl: resolved.href, - }; + return { filesystem: result, specUrl: resolved.href }; } -const fetchFilesystem = async ( +/** + * Fetch the filesystem from the URL. + * It's used for legacy "swagger" blocks. + */ +async function fetchFilesystem( url: string, spaceId: string ): Promise< @@ -65,11 +68,11 @@ const fetchFilesystem = async ( message: string; }; } -> => { +> { 'use cache'; try { cacheTag(getCacheTag({ tag: 'space', space: spaceId })); - return await fetchFilesystemUncached(url); + return await fetchFilesystemNoCache(url); } catch (error) { // To avoid hammering the file with requests, we cache the error for around a minute. cacheLife('minutes'); @@ -86,21 +89,16 @@ const fetchFilesystem = async ( console.error('Unknown error while fetching OpenAPI file:', error); return { error: { code: 'invalid' as const, message: 'Unknown error' } }; } -}; +} -async function fetchFilesystemUncached( - url: string, - options?: { - signal?: AbortSignal; - } -) { +async function fetchFilesystemNoCache(url: string) { + console.log(url); // Wrap the raw string to prevent invalid URLs from being passed to fetch. // This can happen if the URL has whitespace, which is currently handled differently by Cloudflare's implementation of fetch: // https://github.com/cloudflare/workerd/issues/1957 const response = await fetch(new URL(url), { ...noCacheFetchOptions, cache: 'no-store', - signal: options?.signal, }); if (!response.ok) { diff --git a/packages/gitbook/src/lib/openapi/resolveOpenAPIOperationBlock.ts b/packages/gitbook/src/lib/openapi/resolveOpenAPIOperationBlock.ts index fa980bd538..e508a634c9 100644 --- a/packages/gitbook/src/lib/openapi/resolveOpenAPIOperationBlock.ts +++ b/packages/gitbook/src/lib/openapi/resolveOpenAPIOperationBlock.ts @@ -9,10 +9,7 @@ import type { type ResolveOpenAPIOperationBlockResult = ResolveOpenAPIBlockResult; -const weakmap = new WeakMap< - AnyOpenAPIOperationsBlock, - Promise ->(); +const cache = new WeakMap>(); /** * Cache the result of resolving an OpenAPI block. @@ -21,22 +18,24 @@ const weakmap = new WeakMap< export function resolveOpenAPIOperationBlock( args: ResolveOpenAPIBlockArgs ): Promise { - if (weakmap.has(args.block)) { - return weakmap.get(args.block)!; + const inCache = cache.get(args.block); + if (inCache) { + return inCache; } - const result = baseResolveOpenAPIOperationBlock(args); - weakmap.set(args.block, result); - return result; + const promise = resolveOpenAPIOperationBlockNoCache(args); + cache.set(args.block, promise); + return promise; } /** * Resolve OpenAPI operation block. */ -async function baseResolveOpenAPIOperationBlock( +async function resolveOpenAPIOperationBlockNoCache( args: ResolveOpenAPIBlockArgs ): Promise { const { context, block } = args; + if (!block.data.path || !block.data.method) { return { data: null, specUrl: null }; } diff --git a/packages/openapi-parser/src/parse.test.ts b/packages/openapi-parser/src/parse.test.ts index 0d3ab70a8e..fbf1b28cfa 100644 --- a/packages/openapi-parser/src/parse.test.ts +++ b/packages/openapi-parser/src/parse.test.ts @@ -39,7 +39,9 @@ describe('#parseOpenAPI', () => { }); } catch (error) { if (error instanceof OpenAPIParseError) { - expect(error.message).toContain('Invalid OpenAPI document'); + expect(error.message).toContain( + 'Can’t find supported Swagger/OpenAPI version in the provided document, version must be a string.' + ); } } }); diff --git a/packages/openapi-parser/src/v3.ts b/packages/openapi-parser/src/v3.ts index 184a3676e1..a5744c8d4f 100644 --- a/packages/openapi-parser/src/v3.ts +++ b/packages/openapi-parser/src/v3.ts @@ -12,6 +12,19 @@ export async function parseOpenAPIV3(input: ParseOpenAPIInput): Promise