← Public packages
@kody/api-research
Research third-party APIs: registry search, provider discovery, OpenAPI summarize, and client scaffold.
src/fetch-spec-lib.ts
88 lines · 2.3 KB · TypeScriptimport {
BoundedBodyTooLargeError,
readBoundedBody,
} from './bounded-body.ts'
import { getErrorMessage } from './error-message.ts'
import { assertHttpsUrl } from './https.ts'
export const defaultOpenApiSpecMaxBytes = 4_000_000
export const defaultOpenApiSpecTimeoutMs = 30_000
const maxRedirectHops = 5
function isRedirectStatus(status: number): boolean {
return status >= 300 && status < 400
}
export async function fetchOpenApiSpecText(input: {
specUrl: string
maxBytes?: number
timeoutMs?: number
fetchImpl?: typeof fetch
}): Promise<string> {
let url = assertHttpsUrl(input.specUrl, 'OpenAPI spec URL')
const maxBytes = input.maxBytes ?? defaultOpenApiSpecMaxBytes
const timeoutMs = input.timeoutMs ?? defaultOpenApiSpecTimeoutMs
const fetchImpl = input.fetchImpl ?? fetch
let response: Response
let redirected = false
try {
for (let hop = 0; ; hop += 1) {
response = await fetchImpl(url.toString(), {
headers: {
Accept: 'application/json, application/yaml, text/yaml, */*',
},
redirect: 'manual',
signal: AbortSignal.timeout(timeoutMs),
})
if (!isRedirectStatus(response.status)) {
break
}
void response.body?.cancel().catch(() => {})
if (hop >= maxRedirectHops) {
throw new Error(
`OpenAPI spec fetch failed: too many redirects (max ${maxRedirectHops})`,
)
}
const location = response.headers.get('location')
if (location == null || location.trim().length === 0) {
throw new Error(
'OpenAPI spec fetch failed: redirect response missing Location header',
)
}
const nextUrl = new URL(location, url)
url = assertHttpsUrl(nextUrl.toString(), 'OpenAPI spec URL')
redirected = true
}
} catch (cause) {
if (
cause instanceof Error &&
cause.message.startsWith('OpenAPI spec fetch failed:')
) {
throw cause
}
throw new Error(`OpenAPI spec fetch failed: ${getErrorMessage(cause)}`)
}
if (!response.ok) {
throw new Error(
redirected
? 'OpenAPI spec fetch failed: redirect destination did not return the spec'
: `OpenAPI spec fetch failed: HTTP ${response.status} for ${input.specUrl}`,
)
}
try {
return await readBoundedBody(response, maxBytes)
} catch (cause) {
if (cause instanceof BoundedBodyTooLargeError) {
throw new Error(`OpenAPI spec fetch failed: ${cause.message}`)
}
throw cause
}
}