← Public packages
@kody/openapi
Bind an OpenAPI spec and call selected operations with saved integration or secret names.
src/url.ts
46 lines · 1.5 KB · TypeScriptimport { normalizeApiBaseUrl } from './selection.ts'
export function buildOperationUrl(input: {
apiBaseUrl: string
path: string
params: Record<string, string>
query: Record<string, unknown>
}): URL {
const base = normalizeApiBaseUrl(input.apiBaseUrl)
let path = input.path.trim()
path = path.replace(/\{([^}/]+)\}/g, (_match, name: string) => {
if (!(name in input.params)) {
throw new Error(
`Missing required path parameter "${name}" for OpenAPI operation path ${input.path}.`,
)
}
return encodeURIComponent(input.params[name] ?? '')
})
if (!path.startsWith('/') || path.startsWith('//')) {
throw new Error(
`OpenAPI operation path ${JSON.stringify(input.path)} must be relative to the binding apiBaseUrl (a single leading "/"). Spec paths never override the binding host.`,
)
}
const url = new URL(`${base}${path}`)
for (const [key, value] of Object.entries(input.query)) {
if (value == null) continue
if (Array.isArray(value)) {
for (const entry of value) {
if (entry == null) continue
url.searchParams.append(key, String(entry))
}
continue
}
url.searchParams.append(key, String(value))
}
return url
}
export function assertPinnedToApiBaseUrl(url: URL, apiBaseUrl: string) {
const expectedHost = new URL(normalizeApiBaseUrl(apiBaseUrl)).host
if (url.host !== expectedHost) {
throw new Error(
`OpenAPI operation URL host "${url.host}" does not match binding apiBaseUrl host "${expectedHost}". Outbound requests are pinned to the binding apiBaseUrl; spec servers never widen host approval.`,
)
}
}