Skip to content

Gracefully handle OpenAPI fetch errors #3455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions packages/gitbook/src/lib/openapi/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { OpenAPIParseError, parseOpenAPI } from '@gitbook/openapi-parser';
import {
type Filesystem,
OpenAPIParseError,
type OpenAPIParseErrorCode,
parseOpenAPI,
} from '@gitbook/openapi-parser';

import { noCacheFetchOptions } from '@/lib/data';
import { DataFetcherError, noCacheFetchOptions } from '@/lib/data';
import { resolveContentRef } from '@/lib/references';
import { unstable_cacheLife as cacheLife } from 'next/cache';
import { assert } from 'ts-essentials';
Expand Down Expand Up @@ -48,18 +53,35 @@ export async function fetchOpenAPIFilesystem(
};
}

const fetchFilesystem = async (url: string) => {
const fetchFilesystem = async (
url: string
): Promise<
| Filesystem
| {
error: {
code: OpenAPIParseErrorCode;
message: string;
};
}
> => {
'use cache';
try {
return await fetchFilesystemUncached(url);
} catch (error) {
// To avoid hammering the file with requests, we cache the error for around a minute.
cacheLife('minutes');
// Throwing an error inside a "use cache" function obfuscates the error,
// so we need to handle it here and recreates the error outside the cache function.
if (error instanceof OpenAPIParseError) {
cacheLife('seconds');
return { error: { code: error.code, message: error.message } };
}
throw error;
if (error instanceof DataFetcherError) {
return { error: { code: 'invalid' as const, message: 'Failed to fetch OpenAPI file' } };
}
// If the error is not an OpenAPIParseError or DataFetcherError,
// we assume it's an unknown error and return a generic error.
console.error('Unknown error while fetching OpenAPI file:', error);
return { error: { code: 'invalid' as const, message: 'Unknown error' } };
}
};

Expand All @@ -78,7 +100,7 @@ async function fetchFilesystemUncached(
});

if (!response.ok) {
throw new Error(`Failed to fetch OpenAPI file: ${response.status} ${response.statusText}`);
throw new DataFetcherError('Failed to fetch OpenAPI file', response.status);
}

const text = await response.text();
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi-parser/src/error.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ErrorObject } from '@scalar/openapi-parser';

type OpenAPIParseErrorCode =
export type OpenAPIParseErrorCode =
| 'invalid'
| 'parse-v2-in-v3'
| 'v2-conversion'
Expand Down