/*
 * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
 * @generated-id: 63a80782d37e
 */
const DEFAULT_FETCHER = (input, init) => {
    // If input is a Request and init is undefined, Bun will discard the method,
    // headers, body and other options that were set on the request object.
    // Node.js and browers would ignore an undefined init value. This check is
    // therefore needed for interop with Bun.
    if (init == null) {
        return fetch(input);
    }
    else {
        return fetch(input, init);
    }
};
export class HTTPClient {
    options;
    fetcher;
    requestHooks = [];
    requestErrorHooks = [];
    responseHooks = [];
    constructor(options = {}) {
        this.options = options;
        this.fetcher = options.fetcher || DEFAULT_FETCHER;
    }
    async request(request) {
        let req = request;
        for (const hook of this.requestHooks) {
            const nextRequest = await hook(req);
            if (nextRequest) {
                req = nextRequest;
            }
        }
        try {
            const res = await this.fetcher(req);
            for (const hook of this.responseHooks) {
                await hook(res, req);
            }
            return res;
        }
        catch (err) {
            for (const hook of this.requestErrorHooks) {
                await hook(err, req);
            }
            throw err;
        }
    }
    addHook(...args) {
        if (args[0] === "beforeRequest") {
            this.requestHooks.push(args[1]);
        }
        else if (args[0] === "requestError") {
            this.requestErrorHooks.push(args[1]);
        }
        else if (args[0] === "response") {
            this.responseHooks.push(args[1]);
        }
        else {
            throw new Error(`Invalid hook type: ${args[0]}`);
        }
        return this;
    }
    removeHook(...args) {
        let target;
        if (args[0] === "beforeRequest") {
            target = this.requestHooks;
        }
        else if (args[0] === "requestError") {
            target = this.requestErrorHooks;
        }
        else if (args[0] === "response") {
            target = this.responseHooks;
        }
        else {
            throw new Error(`Invalid hook type: ${args[0]}`);
        }
        const index = target.findIndex((v) => v === args[1]);
        if (index >= 0) {
            target.splice(index, 1);
        }
        return this;
    }
    clone() {
        const child = new HTTPClient(this.options);
        child.requestHooks = this.requestHooks.slice();
        child.requestErrorHooks = this.requestErrorHooks.slice();
        child.responseHooks = this.responseHooks.slice();
        return child;
    }
}
// A semicolon surrounded by optional whitespace characters is used to separate
// segments in a media type string.
const mediaParamSeparator = /\s*;\s*/g;
export function matchContentType(response, pattern) {
    // `*` is a special case which means anything is acceptable.
    if (pattern === "*") {
        return true;
    }
    let contentType = response.headers.get("content-type")?.trim() || "application/octet-stream";
    contentType = contentType.toLowerCase();
    const wantParts = pattern.toLowerCase().trim().split(mediaParamSeparator);
    const [wantType = "", ...wantParams] = wantParts;
    if (wantType.split("/").length !== 2) {
        return false;
    }
    const gotParts = contentType.split(mediaParamSeparator);
    const [gotType = "", ...gotParams] = gotParts;
    const [type = "", subtype = ""] = gotType.split("/");
    if (!type || !subtype) {
        return false;
    }
    if (wantType !== "*/*" &&
        gotType !== wantType &&
        `${type}/*` !== wantType &&
        `*/${subtype}` !== wantType) {
        return false;
    }
    if (gotParams.length < wantParams.length) {
        return false;
    }
    const params = new Set(gotParams);
    for (const wantParam of wantParams) {
        if (!params.has(wantParam)) {
            return false;
        }
    }
    return true;
}
const codeRangeRE = new RegExp("^[0-9]xx$", "i");
export function matchStatusCode(response, codes) {
    const actual = `${response.status}`;
    const expectedCodes = Array.isArray(codes) ? codes : [codes];
    if (!expectedCodes.length) {
        return false;
    }
    return expectedCodes.some((ec) => {
        const code = `${ec}`;
        if (code === "default") {
            return true;
        }
        if (!codeRangeRE.test(`${code}`)) {
            return code === actual;
        }
        const expectFamily = code.charAt(0);
        if (!expectFamily) {
            throw new Error("Invalid status code range");
        }
        const actualFamily = actual.charAt(0);
        if (!actualFamily) {
            throw new Error(`Invalid response status code: ${actual}`);
        }
        return actualFamily === expectFamily;
    });
}
export function matchResponse(response, code, contentTypePattern) {
    return (matchStatusCode(response, code) &&
        matchContentType(response, contentTypePattern));
}
/**
 * Uses various heurisitics to determine if an error is a connection error.
 */
export function isConnectionError(err) {
    if (typeof err !== "object" || err == null) {
        return false;
    }
    // Covers fetch in Deno as well
    const isBrowserErr = err instanceof TypeError &&
        err.message.toLowerCase().startsWith("failed to fetch");
    const isNodeErr = err instanceof TypeError &&
        err.message.toLowerCase().startsWith("fetch failed");
    const isBunErr = "name" in err && err.name === "ConnectionError";
    const isGenericErr = "code" in err &&
        typeof err.code === "string" &&
        err.code.toLowerCase() === "econnreset";
    return isBrowserErr || isNodeErr || isGenericErr || isBunErr;
}
/**
 * Uses various heurisitics to determine if an error is a timeout error.
 */
export function isTimeoutError(err) {
    if (typeof err !== "object" || err == null) {
        return false;
    }
    // Fetch in browser, Node.js, Bun, Deno
    const isNative = "name" in err && err.name === "TimeoutError";
    const isLegacyNative = "code" in err && err.code === 23;
    // Node.js HTTP client and Axios
    const isGenericErr = "code" in err &&
        typeof err.code === "string" &&
        err.code.toLowerCase() === "econnaborted";
    return isNative || isLegacyNative || isGenericErr;
}
/**
 * Uses various heurisitics to determine if an error is a abort error.
 */
export function isAbortError(err) {
    if (typeof err !== "object" || err == null) {
        return false;
    }
    // Fetch in browser, Node.js, Bun, Deno
    const isNative = "name" in err && err.name === "AbortError";
    const isLegacyNative = "code" in err && err.code === 20;
    // Node.js HTTP client and Axios
    const isGenericErr = "code" in err &&
        typeof err.code === "string" &&
        err.code.toLowerCase() === "econnaborted";
    return isNative || isLegacyNative || isGenericErr;
}
//# sourceMappingURL=http.js.map