From 2dfc9729304f40cae97838a370c0eedcc54f62f7 Mon Sep 17 00:00:00 2001 From: Ajai Shankar Date: Sun, 19 Feb 2023 23:35:49 -0600 Subject: [PATCH] feat: res default to bruno query --- packages/bruno-js/package.json | 5 +- packages/bruno-js/src/get.js | 128 -------------------------- packages/bruno-js/src/utils.js | 31 ++----- packages/bruno-js/tests/get.spec.js | 47 ---------- packages/bruno-js/tests/utils.spec.js | 27 +++++- 5 files changed, 36 insertions(+), 202 deletions(-) delete mode 100644 packages/bruno-js/src/get.js delete mode 100644 packages/bruno-js/tests/get.spec.js diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index 2a907eee6..88680c26d 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -21,6 +21,7 @@ "lodash": "^4.17.21", "moment": "^2.29.4", "nanoid": "3.3.4", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "@usebruno/query": "0.1.0" } -} +} \ No newline at end of file diff --git a/packages/bruno-js/src/get.js b/packages/bruno-js/src/get.js deleted file mode 100644 index d58e24bb7..000000000 --- a/packages/bruno-js/src/get.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Gets property values for all items in source array - */ -const arrayGet = (source, prop) => { - if (!Array.isArray(source)) return []; - - const results = []; - - source.forEach(item => { - const value = item[prop]; - if (value != null) { - results.push(...Array.isArray(value) ? value : [value]); - } - }); - - return results; -}; - -/** - * Recursively collects property values into results - */ -const deepGet = (source, prop, results) => { - if (source == null || typeof source !== 'object') return; - - if (Array.isArray(source)) { - source.forEach(item => deepGet(item, prop, results)); - } else { - for (const key in source) { - if (key === prop) { - const value = source[prop]; - results.push(...Array.isArray(value) ? value : [value]); - } else { - deepGet(source[key], prop, results); - } - } - } -}; - -/** - * Gets property value(s) from source - */ -const baseGet = (source, prop, deep = false) => { - if (source == null || typeof source !== 'object') return; - - if (!deep) { - return Array.isArray(source) ? arrayGet(source, prop) : source[prop]; - } else { - const results = []; - deepGet(source, prop, results); - return results.filter(value => value != null); - } -}; - -/** - * Apply filter on source array or object - */ -const applyFilter = (source, predicate, single = false) => { - const list = Array.isArray(source) ? source : [source]; - const result = list.filter(predicate); - return single ? result[0] : result; -}; - -/** - * Supercharged getter with deep navigation and filter support - * - * 1. Easy array navigation - * ```js - * get(data, 'customer.orders.items.amount') - * ``` - * 2. Deep navigation .. double dots - * ```js - * get(data, '..items.amount') - * ``` - * 3. Array indexing - * ```js - * get(data, '..items[0].amount') - * ``` - * 4. Array filtering [?] with corresponding js filter - * ```js - * get(data, '..items[?].amount', i => i.amount > 20) - * ``` - */ -function get(source, path, ...filters) { - const paths = path - .replace(/\s+/g, '') - .split(/(\.{1,2}|\[\?\]|\[\d+\])/g) // ["..", "items", "[?]", ".", "amount", "[0]" ] - .filter(s => s.length > 0) - .map(str => { - str = str.replace(/\[|\]/g, ''); - const index = parseInt(str); - return isNaN(index) ? str : index; - }); - - let index = 0, lookbehind = '', filterIndex = 0; - - while (source != null && index < paths.length) { - const token = paths[index++]; - - switch (true) { - case token === "..": - case token === ".": - break; - case token === "?": - const filter = filters[filterIndex++]; - if (filter == null) - throw new Error(`missing filter for ${lookbehind}`); - const single = !Array.isArray(source); - source = applyFilter(source, filter, single); - break; - case typeof token === 'number': - source = source[token]; - break; - default: - source = baseGet(source, token, lookbehind === ".."); - if (Array.isArray(source) && !source.length) { - source = undefined; - } - } - - lookbehind = token; - } - - return source; -} - -module.exports = { - get -}; diff --git a/packages/bruno-js/src/utils.js b/packages/bruno-js/src/utils.js index 151bb6014..262926905 100644 --- a/packages/bruno-js/src/utils.js +++ b/packages/bruno-js/src/utils.js @@ -1,5 +1,5 @@ const jsonQuery = require('json-query'); -const { get } = require("./get"); +const { get } = require("@usebruno/query"); const JS_KEYWORDS = ` break case catch class const continue debugger default delete do @@ -61,9 +61,8 @@ const evaluateJsExpression = (expression, context) => { }; const createResponseParser = (response = {}) => { - const res = (expr) => { - const output = jsonQuery(expr, { data: response.data }); - return output ? output.value : null; + const res = (expr, ...fns) => { + return get(response.data, expr, ...fns); }; res.status = response.status; @@ -71,26 +70,10 @@ const createResponseParser = (response = {}) => { res.headers = response.headers; res.body = response.data; - /** - * Get supports deep object navigation and filtering - * 1. Easy array navigation - * ```js - * res.get('customer.orders.items.amount') - * ``` - * 2. Deep navigation .. double dots - * ```js - * res.get('..items.amount') - * ``` - * 3. Array indexing - * ```js - * res.get('..items[0].amount') - * ``` - * 4. Array filtering [?] with corresponding js filter - * ```js - * res.get('..items[?].amount', i => i.amount > 20) - * ``` - */ - res.get = (path, ...filters) => get(response.data, path, ...filters); + res.jq = (expr) => { + const output = jsonQuery(expr, { data: response.data }); + return output ? output.value : null; + }; return res; }; diff --git a/packages/bruno-js/tests/get.spec.js b/packages/bruno-js/tests/get.spec.js deleted file mode 100644 index 7e251ae70..000000000 --- a/packages/bruno-js/tests/get.spec.js +++ /dev/null @@ -1,47 +0,0 @@ -const { filter } = require("lodash"); -const { get } = require("../src/get"); - -const data = { - customer: { - address: { - city: "bangalore" - }, - orders: [ - { - id: "order-1", - items: [ - { id: 1, amount: 10 }, - { id: 2, amount: 20 }, - ] - }, - { - id: "order-2", - items: [ - { id: 3, amount: 30, }, - { id: 4, amount: 40 } - ] - } - ] - }, -}; - -describe("get", () => { - it.each([ - ["customer.address.city", "bangalore"], - ["customer.orders.items.amount", [10, 20, 30, 40]], - ["customer.orders.items.amount[0]", 10], - ["..items.amount", [10, 20, 30, 40]], - ["..amount", [10, 20, 30, 40]], - ["..items.amount[0]", 10], - ["..items[0].amount", 10], - ["..items[?].amount", [40], (i) => i.amount > 30], // [?] filter - ["..id", ["order-1", 1, 2, "order-2", 3, 4]], // all ids - ["customer.orders.foo", undefined], - ["..customer.foo", undefined], - ["..address", [{ city: "bangalore" }]], // .. will return array - ["..address[0]", { city: "bangalore" }], - ["..items..amount[?][0]", 40, amt => amt > 30] - ])("%s should be %j %s", (expr, result, filter = undefined) => { - expect(get(data, expr, filter)).toEqual(result); - }); -}); diff --git a/packages/bruno-js/tests/utils.spec.js b/packages/bruno-js/tests/utils.spec.js index 6d5212407..ef8973542 100644 --- a/packages/bruno-js/tests/utils.spec.js +++ b/packages/bruno-js/tests/utils.spec.js @@ -1,4 +1,5 @@ -const { evaluateJsExpression, internalExpressionCache: cache } = require("../src/utils"); +const { describe, it, expect } = require("@jest/globals"); +const { evaluateJsExpression, internalExpressionCache: cache, createResponseParser } = require("../src/utils"); describe("utils", () => { describe("expression evaluation", () => { @@ -112,4 +113,28 @@ describe("utils", () => { expect(result).toBe(startTime); }); }); + + describe("response parser", () => { + const res = createResponseParser({ + status: 200, + data: { + order: { + items: [ + { id: 1, amount: 10 }, + { id: 2, amount: 20 } + ] + } + } + }); + + it("should default to bruno query", () => { + const value = res("..items[?].amount[0]", i => i.amount > 10); + expect(value).toBe(20); + }); + + it("should allow json-query", () => { + const value = res.jq("order.items[amount > 10].amount"); + expect(value).toBe(20); + }); + }); });