mirror of
https://github.com/usebruno/bruno.git
synced 2025-06-21 20:41:41 +02:00
feat: res default to bruno query
This commit is contained in:
parent
4fdfdaf2cb
commit
2dfc972930
@ -21,6 +21,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0",
|
||||||
|
"@usebruno/query": "0.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
|
||||||
};
|
|
@ -1,5 +1,5 @@
|
|||||||
const jsonQuery = require('json-query');
|
const jsonQuery = require('json-query');
|
||||||
const { get } = require("./get");
|
const { get } = require("@usebruno/query");
|
||||||
|
|
||||||
const JS_KEYWORDS = `
|
const JS_KEYWORDS = `
|
||||||
break case catch class const continue debugger default delete do
|
break case catch class const continue debugger default delete do
|
||||||
@ -61,9 +61,8 @@ const evaluateJsExpression = (expression, context) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createResponseParser = (response = {}) => {
|
const createResponseParser = (response = {}) => {
|
||||||
const res = (expr) => {
|
const res = (expr, ...fns) => {
|
||||||
const output = jsonQuery(expr, { data: response.data });
|
return get(response.data, expr, ...fns);
|
||||||
return output ? output.value : null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
res.status = response.status;
|
res.status = response.status;
|
||||||
@ -71,26 +70,10 @@ const createResponseParser = (response = {}) => {
|
|||||||
res.headers = response.headers;
|
res.headers = response.headers;
|
||||||
res.body = response.data;
|
res.body = response.data;
|
||||||
|
|
||||||
/**
|
res.jq = (expr) => {
|
||||||
* Get supports deep object navigation and filtering
|
const output = jsonQuery(expr, { data: response.data });
|
||||||
* 1. Easy array navigation
|
return output ? output.value : null;
|
||||||
* ```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);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@ -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("utils", () => {
|
||||||
describe("expression evaluation", () => {
|
describe("expression evaluation", () => {
|
||||||
@ -112,4 +113,28 @@ describe("utils", () => {
|
|||||||
expect(result).toBe(startTime);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user