"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestRuleBuilder = void 0;
const lodash_1 = require("lodash");
const request_handler_definitions_1 = require("./request-handler-definitions");
const util_1 = require("../../util/util");
const base_rule_builder_1 = require("../base-rule-builder");
const matchers_1 = require("../matchers");
/**
 * @class RequestRuleBuilder

 * A builder for defining mock rules. Create one using a method like
 * `.forGet(path)` or `.forPost(path)` on a Mockttp instance, then call
 * whatever methods you'd like here to define more precise request
 * matching behaviour, control how the request is handled, and how
 * many times this rule should be applied.
 *
 * When you're done, call a `.thenX()` method to register the configured rule
 * with the server. These return a promise for a MockedEndpoint, which can be
 * used to verify the details of the requests matched by the rule.
 *
 * This returns a promise because rule registration can be asynchronous,
 * either when using a remote server or testing in the browser. Wait for the
 * promise returned by `.thenX()` methods to guarantee that the rule has taken
 * effect before sending requests to it.
 */
class RequestRuleBuilder extends base_rule_builder_1.BaseRuleBuilder {
    constructor(methodOrAddRule, path, addRule) {
        super();
        // Add the basic method and path matchers inititally, if provided:
        const method = methodOrAddRule instanceof Function ? undefined : methodOrAddRule;
        if (method === undefined && path === undefined) {
            this.matchers.push(new matchers_1.WildcardMatcher());
        }
        else {
            if (method !== undefined) {
                this.matchers.push(new matchers_1.MethodMatcher(method));
            }
            if (path instanceof RegExp) {
                this.matchers.push(new matchers_1.RegexPathMatcher(path));
            }
            else if (typeof path === 'string') {
                this.matchers.push(new matchers_1.SimplePathMatcher(path));
            }
        }
        // Store the addRule callback:
        if (methodOrAddRule instanceof Function) {
            this.addRule = methodOrAddRule;
        }
        else {
            this.addRule = addRule;
        }
    }
    thenReply(status, dataOrMessage, dataOrHeaders, headersOrTrailers, trailers) {
        let data;
        let statusMessage;
        let headers;
        if ((0, lodash_1.isBuffer)(dataOrHeaders) || (0, lodash_1.isString)(dataOrHeaders)) {
            data = dataOrHeaders;
            statusMessage = dataOrMessage;
            headers = headersOrTrailers;
        }
        else {
            data = dataOrMessage;
            headers = dataOrHeaders;
            trailers = headersOrTrailers;
        }
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.SimpleHandlerDefinition(status, statusMessage, data, headers, trailers)
        };
        return this.addRule(rule);
    }
    /**
     * Reply to matched requests with the given status & JSON and (optionally)
     * extra headers.
     *
     * This method is (approximately) shorthand for:
     * server.forGet(...).thenReply(status, JSON.stringify(data), { 'Content-Type': 'application/json' })
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    thenJson(status, data, headers = {}) {
        const jsonData = JSON.stringify(data);
        headers = (0, lodash_1.merge)({
            'Content-Type': 'application/json',
            'Content-Length': (0, util_1.byteLength)(jsonData).toString(),
            'Connection': 'keep-alive'
            // ^ Neither strictly required, but without both Node will close the server
            // connection after the response is sent, which can confuse clients.
        }, headers);
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.SimpleHandlerDefinition(status, undefined, jsonData, headers)
        };
        return this.addRule(rule);
    }
    /**
     * Call the given callback for any matched requests that are received,
     * and build a response from the result.
     *
     * The callback should return a response object with the fields as
     * defined by {@link CallbackResponseMessageResult} to define the response,
     * or the string 'close' to immediately close the connection. The callback
     * can be asynchronous, in which case it should return this value wrapped
     * in a promise.
     *
     * If the callback throws an exception, the server will return a 500
     * with the exception message.
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    thenCallback(callback) {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.CallbackHandlerDefinition(callback)
        };
        return this.addRule(rule);
    }
    /**
     * Respond immediately with the given status (and optionally, headers),
     * and then stream the given stream directly as the response body.
     *
     * Note that streams can typically only be read once, and as such
     * this rule will only successfully trigger once. Subsequent requests
     * will receive a 500 and an explanatory error message. To mock
     * repeated requests with streams, create multiple streams and mock
     * them independently.
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    thenStream(status, stream, headers) {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.StreamHandlerDefinition(status, stream, headers)
        };
        return this.addRule(rule);
    }
    thenFromFile(status, pathOrMessage, pathOrHeaders, headers) {
        let path;
        let statusMessage;
        if ((0, lodash_1.isString)(pathOrHeaders)) {
            path = pathOrHeaders;
            statusMessage = pathOrMessage;
        }
        else {
            path = pathOrMessage;
            headers = pathOrHeaders;
        }
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.FileHandlerDefinition(status, statusMessage, path, headers)
        };
        return this.addRule(rule);
    }
    /**
     * Pass matched requests through to their real destination. This works
     * for proxied requests only, direct requests will be rejected with
     * an error.
     *
     * This method takes options to configure how the request is passed
     * through. See {@link PassThroughHandlerOptions} for the full details
     * of the options available.
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    thenPassThrough(options) {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.PassThroughHandlerDefinition(options)
        };
        return this.addRule(rule);
    }
    /**
     * Forward matched requests on to the specified forwardToUrl. The url
     * specified must not include a path. Otherwise, an error is thrown.
     * The path portion of the original request url is used instead.
     *
     * The url may optionally contain a protocol. If it does, it will override
     * the protocol (and potentially the port, if unspecified) of the request.
     * If no protocol is specified, the protocol (and potentially the port)
     * of the original request URL will be used instead.
     *
     * This method takes options to configure how the request is passed
     * through. See {@link PassThroughHandlerOptions} for the full details
     * of the options available.
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    async thenForwardTo(forwardToLocation, options = {}) {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.PassThroughHandlerDefinition({
                ...options,
                forwarding: {
                    ...options.forwarding,
                    targetHost: forwardToLocation
                }
            })
        };
        return this.addRule(rule);
    }
    /**
     * Close connections that match this rule immediately, without
     * any status code or response.
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    thenCloseConnection() {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.CloseConnectionHandlerDefinition()
        };
        return this.addRule(rule);
    }
    /**
     * Reset connections that match this rule immediately, sending a TCP
     * RST packet directly, without any status code or response, and without
     * cleanly closing the TCP connection.
     *
     * This is only supported in Node.js versions (>=16.17, >=18.3.0, or
     * later), where `net.Socket` includes the `resetAndDestroy` method.
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    thenResetConnection() {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.ResetConnectionHandlerDefinition()
        };
        return this.addRule(rule);
    }
    /**
     * Hold open connections that match this rule, but never respond
     * with anything at all, typically causing a timeout on the client side.
     *
     * Calling this method registers the rule with the server, so it
     * starts to handle requests.
     *
     * This method returns a promise that resolves with a mocked endpoint.
     * Wait for the promise to confirm that the rule has taken effect
     * before sending requests to be matched. The mocked endpoint
     * can be used to assert on the requests matched by this rule.
     *
     * @category Responses
     */
    thenTimeout() {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.TimeoutHandlerDefinition()
        };
        return this.addRule(rule);
    }
    /**
     * Send a successful JSON-RPC response to a JSON-RPC request. The response data
     * can be any JSON-serializable value. If a matching request is received that
     * is not a valid JSON-RPC request, it will be rejected with an HTTP error.
     *
     * @category Responses
     */
    thenSendJsonRpcResult(result) {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.JsonRpcResponseHandlerDefinition({ result })
        };
        return this.addRule(rule);
    }
    /**
     * Send a failing error JSON-RPC response to a JSON-RPC request. The error data
     * can be any JSON-serializable value. If a matching request is received that
     * is not a valid JSON-RPC request, it will be rejected with an HTTP error.
     *
     * @category Responses
     */
    thenSendJsonRpcError(error) {
        const rule = {
            ...this.buildBaseRuleData(),
            handler: new request_handler_definitions_1.JsonRpcResponseHandlerDefinition({ error })
        };
        return this.addRule(rule);
    }
}
exports.RequestRuleBuilder = RequestRuleBuilder;
//# sourceMappingURL=request-rule-builder.js.map