"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const envoy_http_request_params_1 = require("./envoy-http-request-params");
const envoy_context_1 = require("./envoy-context");
const envoy_fetch_1 = require("./envoy-fetch");
/**
 * json header
 * @internal
 */
exports.APPLICATION_JSON = "application/json";
/**
 * a high level http client that will send traffic to Envoy:
 * 1. all HTTP GET / POST ... will be wrapped as async signature
 * 2. doing JSON RESET API common works: accepting object, return objects, only works with `application/json`
 * 3. none 2XX request will be throw as Error
 */
class EnvoyHttpClient {
    /**
     * init the client
     * @param ctx context, you can give either EnvoyContext, Metadata or HTTP Header
     *  the last two will be convert to EnvoyContext
     */
    constructor(ctx) {
        if (ctx instanceof envoy_context_1.default) {
            this.envoyContext = ctx;
        }
        else {
            this.envoyContext = new envoy_context_1.default(ctx);
        }
    }
    /**
     * the common logic for json processing and error handling
     * @param response response from fetch
     */
    returnJsonOrError(response) {
        return __awaiter(this, void 0, void 0, function* () {
            // TODO considering return the following metadata:
            // response.overloaded
            // response.upstreamServiceTime
            const $statusCode = response.status;
            if ($statusCode === 204) {
                return undefined;
            }
            const contentType = response.headers.get("content-type");
            if (!contentType || (contentType !== "application/json" && !contentType.startsWith("text/"))) {
                const err = new Error(`Unexpected content type: ${contentType}, http status: ${$statusCode}`);
                const body = yield response.text();
                Object.assign(err, { $statusCode, body });
                throw err;
            }
            if (contentType === "application/json") {
                const result = yield response.json();
                if ($statusCode >= 400) {
                    result.$statusCode = result.$statusCode || $statusCode;
                    throw result;
                }
                return result;
            }
            const text = yield response.text();
            if ($statusCode !== 200) {
                const error = new Error(text);
                Object.assign(error, { $statusCode });
                throw error;
            }
            return text;
        });
    }
    actionWithoutBody(method, url, init) {
        return __awaiter(this, void 0, void 0, function* () {
            const param = new envoy_http_request_params_1.default(this.envoyContext, init);
            const res = yield envoy_fetch_1.default(param, url, {
                method,
                headers: {
                    accept: exports.APPLICATION_JSON
                }
            });
            return this.returnJsonOrError(res);
        });
    }
    /**
     * send a GET request and expecting return json or empty
     * @param url the URL to get
     * @param init the params for the request, like retry, timeout
     * @throws Error for none 2XX request, a $statusCode will be available in the error object
     */
    get(url, init) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.actionWithoutBody("GET", url, init);
        });
    }
    /**
     * send a DELETE request and expecting return json or empty
     * @param url the URL to get
     * @param init the params for the request, like retry, timeout
     * @throws Error for none 2XX request, a $statusCode will be available in the error object
     */
    delete(url, init) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.actionWithoutBody("DELETE", url, init);
        });
    }
    actionWithBody(method, url, body, init) {
        return __awaiter(this, void 0, void 0, function* () {
            const param = new envoy_http_request_params_1.default(this.envoyContext, init);
            const res = yield envoy_fetch_1.default(param, url, {
                method,
                headers: {
                    "content-type": exports.APPLICATION_JSON,
                    // tslint:disable-next-line:object-literal-key-quotes
                    accept: exports.APPLICATION_JSON
                },
                body: JSON.stringify(body)
            });
            return this.returnJsonOrError(res);
        });
    }
    /**
     * send a POST request and expecting return json or empty
     * @param url the URL to get
     * @param body the request object, will be serialize to JSON when sending out
     * @param init the params for the request, like retry, timeout
     * @throws Error for none 2XX request, a $statusCode will be available in the error object
     */
    post(url, body, init) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.actionWithBody("POST", url, body, init);
        });
    }
    /**
     * send a PATCH request and expecting return json or empty
     * @param url the URL to get
     * @param body the request object, will be serialize to JSON when sending out
     * @param init the params for the request, like retry, timeout
     * @throws Error for none 2XX request, a $statusCode will be available in the error object
     */
    patch(url, body, init) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.actionWithBody("PATCH", url, body, init);
        });
    }
    /**
     * send a PUT request and expecting return json or empty
     * @param url the URL to get
     * @param body the request object, will be serialize to JSON when sending out
     * @param init the params for the request, like retry, timeout
     * @throws Error for none 2XX request, a $statusCode will be available in the error object
     */
    put(url, body, init) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.actionWithBody("PUT", url, body, init);
        });
    }
}
exports.default = EnvoyHttpClient;
//# sourceMappingURL=envoy-http-client.js.map