Repository: louislam/uptime-kuma
File: src/util.ts (TypeScript)

Code before:
 * @throws Error if the evaluation returns undefined.
 */
export async function evaluateJsonQuery(data: any, jsonPath: string, jsonPathOperator: string, expectedValue: any): Promise<{ status: boolean; response: any }> {
    // Attempt to parse data as JSON; if unsuccessful, handle based on data type.
    let response: any;
    try {
        response = JSON.parse(data);
    } catch {
        response = (typeof data === "object" || typeof data === "number") && !Buffer.isBuffer(data) ? data : data.toString();
    }

    try {
        // If a JSON path is provided, pre-evaluate the data using it.
        response = (jsonPath) ? await jsonata(jsonPath).evaluate(response) : response;

        if (response === null || response === undefined) {
            throw new Error("Empty or undefined response. Check query syntax and response structure");
        }

        // Check for arrays: JSONata filter expressions like .[predicate] always return arrays
        if (Array.isArray(response)) {
            const responseStr = JSON.stringify(response);
            const truncatedResponse = responseStr.length > 25 ? responseStr.substring(0, 25) + "..." : responseStr;
            throw new Error(
                "JSON query returned an array (" + truncatedResponse + "), but a primitive value is required for comparison. " +
                "Modify your query to return a single value, e.g., append [0] to get the first element, or use an aggregation function like $count(), $sum(), or $boolean()."
            );
        }

        if (typeof response === "object" || response instanceof Date || typeof response === "function") {
            throw new Error(`The post-JSON query evaluated response from the server is of type ${typeof response}, which cannot be directly compared to the expected value`);
        }

        // Perform the comparison logic using the chosen operator
        let jsonQueryExpression;
        switch (jsonPathOperator) {
            case ">":
            case ">=":
            case "<":
            case "<=":
                jsonQueryExpression = `$number($.value) ${jsonPathOperator} $number($.expected)`;
                break;
            case "!=":
                jsonQueryExpression = "$.value != $.expected";
                break;
            case "==":
                jsonQueryExpression = "$.value = $.expected";
                break;
            case "contains":
                jsonQueryExpression = "$contains($.value, $.expected)";
                break;

Review comment (suggestion):
```suggestion
            const truncatedResponse = responseStr.length > 25 ? responseStr.substring(0, 25) + "...]" : responseStr;
            throw new Error(
                "JSON query returned the array " + truncatedResponse + ", but a primitive value is required. " +
                "Modify your query to return a single value via [0] to get the first element or use an aggregation like $count(), $sum() or $boolean()."
```

Code after:
 * @throws Error if the evaluation returns undefined.
 */
export async function evaluateJsonQuery(data: any, jsonPath: string, jsonPathOperator: string, expectedValue: any): Promise<{ status: boolean; response: any }> {
    // Attempt to parse data as JSON; if unsuccessful, handle based on data type.
    let response: any;
    try {
        response = JSON.parse(data);
    } catch {
        response = (typeof data === "object" || typeof data === "number") && !Buffer.isBuffer(data) ? data : data.toString();
    }

    try {
        // If a JSON path is provided, pre-evaluate the data using it.
        response = (jsonPath) ? await jsonata(jsonPath).evaluate(response) : response;

        if (response === null || response === undefined) {
            throw new Error("Empty or undefined response. Check query syntax and response structure");
        }

        // Check for arrays: JSONata filter expressions like .[predicate] always return arrays
        if (Array.isArray(response)) {
            const responseStr = JSON.stringify(response);
            const truncatedResponse = responseStr.length > 25 ? responseStr.substring(0, 25) + "...]" : responseStr;
            throw new Error(
                "JSON query returned the array " + truncatedResponse + ", but a primitive value is required. " +
                "Modify your query to return a single value via [0] to get the first element or use an aggregation like $count(), $sum() or $boolean()."
            );
        }

        if (typeof response === "object" || response instanceof Date || typeof response === "function") {
            throw new Error(`The post-JSON query evaluated response from the server is of type ${typeof response}, which cannot be directly compared to the expected value`);
        }

        // Perform the comparison logic using the chosen operator
        let jsonQueryExpression;
        switch (jsonPathOperator) {
            case ">":
            case ">=":
            case "<":
            case "<=":
                jsonQueryExpression = `$number($.value) ${jsonPathOperator} $number($.expected)`;
                break;
            case "!=":
                jsonQueryExpression = "$.value != $.expected";
                break;
            case "==":
                jsonQueryExpression = "$.value = $.expected";
                break;
            case "contains":
                jsonQueryExpression = "$contains($.value, $.expected)";
                break;