Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/compile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ describe('error handling', () => {
{ name: 'Joe', age: 32, scores: [6.1, 8.1] }
]
}
const query = ['pipe', ['get', 'participants'], ['map', ['pipe', ['get', 'scores'], ['sum']]]]
const query = [
'pipe',
['get', 'participants'],
['map', ['pipe', ['get', 'scores'], ['map', ['round']], ['sum']]]
]

let actualErr = undefined
try {
Expand All @@ -103,15 +107,18 @@ describe('error handling', () => {
actualErr = err
}

expect(actualErr?.message).toBe('Array expected')
expect(actualErr?.message).toBe("Cannot read properties of null (reading 'map')")
expect(actualErr?.jsonquery).toEqual([
{ data: scoreData, query },
{
data: scoreData.participants,
query: ['map', ['pipe', ['get', 'scores'], ['sum']]]
query: ['map', ['pipe', ['get', 'scores'], ['map', ['round']], ['sum']]]
},
{ data: { name: 'Emily', age: 19 }, query: ['pipe', ['get', 'scores'], ['sum']] },
{ data: null, query: ['sum'] }
{
data: { name: 'Emily', age: 19 },
query: ['pipe', ['get', 'scores'], ['map', ['round']], ['sum']]
},
{ data: null, query: ['map', ['round']] }
])
})
})
Expand Down
55 changes: 29 additions & 26 deletions src/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,15 @@ export function buildFunction(fn: (...args: unknown[]) => unknown): FunctionBuil
const sortableTypes = { boolean: 0, number: 1, string: 2 }
const otherTypes = 3

const gt = (a: unknown, b: unknown) => {
if (typeof a !== typeof b || !((typeof a) in sortableTypes)) {
throwTypeError('Two numbers, strings, or booleans expected')
}

return a > b
}
const gt = (a: unknown, b: unknown) =>
typeof a === typeof b && (typeof a) in sortableTypes ? a > b : false

const gte = (a: unknown, b: unknown) => isEqual(a, b) || gt(a, b)
const lt = (a: unknown, b: unknown) => gt(b, a)
const lte = (a: unknown, b: unknown) => gte(b, a)

const lt = (a: unknown, b: unknown) =>
typeof a === typeof b && (typeof a) in sortableTypes ? a < b : false

const lte = (a: unknown, b: unknown) => isEqual(a, b) || lt(a, b)

export const functions: FunctionBuildersMap = {
pipe: (...entries: JSONQuery[]) => {
Expand Down Expand Up @@ -277,13 +275,22 @@ export const functions: FunctionBuildersMap = {
values: () => Object.values,

prod: () => (data: number[]) => reduce(data, (a, b) => a * b),
sum: () => (data: number[]) => reduce(data, (a, b) => a + b, 0),
average: () => (data: number[]) => reduce(data, (a, b) => a + b) / data.length,
min: () => (data: number[]) => reduce(data, (a, b) => Math.min(a, b), null),
max: () => (data: number[]) => reduce(data, (a, b) => Math.max(a, b), null),

and: buildFunction((...args: unknown[]) => reduce(args, (a, b) => !!(a && b))),
or: buildFunction((...args: unknown[]) => reduce(args, (a, b) => !!(a || b))),
sum: () => (data: number[]) =>
isArray(data) ? data.reduce((a, b) => a + b, 0) : throwArrayExpected(),

average: () => (data: number[]) =>
isArray(data)
? data.length > 0
? data.reduce((a, b) => a + b) / data.length
: null
: throwArrayExpected(),

min: () => (data: number[]) => reduce(data, (a, b) => Math.min(a, b)),
max: () => (data: number[]) => reduce(data, (a, b) => Math.max(a, b)),

and: buildFunction((...data: unknown[]) => reduce(data, (a, b) => !!(a && b))),
or: buildFunction((...data: unknown[]) => reduce(data, (a, b) => !!(a || b))),
not: buildFunction((a: unknown) => !a),

exists: (queryGet: JSONQueryFunction) => {
Expand Down Expand Up @@ -355,26 +362,22 @@ export const functions: FunctionBuildersMap = {

const truthy = (x: unknown) => x !== null && x !== 0 && x !== false

const reduce = <T>(
data: T[],
callback: (previousValue: T, currentValue: T) => T,
initialValue?: T
): T => {
const reduce = <T>(data: T[], callback: (previousValue: T, currentValue: T) => T): T => {
if (!isArray(data)) {
throwTypeError('Array expected')
}

if (initialValue !== undefined) {
return data.reduce(callback, initialValue)
throwArrayExpected()
}

if (data.length === 0) {
throwTypeError('Non-empty array expected')
return null
}

return data.reduce(callback)
}

const throwArrayExpected = () => {
throwTypeError('Array expected')
}

export const throwTypeError = (message: string) => {
throw new TypeError(message)
}
130 changes: 33 additions & 97 deletions test-suite/compile.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -1127,12 +1127,12 @@
},
{
"category": "prod",
"description": "should throw an error when calculating the prod of an empty array",
"description": "should return null when calculating the prod of an empty array",
"tests": [
{
"input": [],
"query": ["prod"],
"throws": "Non-empty array expected"
"output": null
}
]
},
Expand All @@ -1157,18 +1157,18 @@
},
{
"category": "average",
"description": "should throw an error when calculating the average of an empty array",
"description": "should return null when calculating the average of an empty array",
"tests": [
{
"input": [],
"query": ["average"],
"throws": "Non-empty array expected"
"output": null
}
]
},
{
"category": "average",
"description": "should throw an error when calculating the average a string",
"description": "should throw an error when calculating the average of a string",
"tests": [
{
"input": "abc",
Expand Down Expand Up @@ -1331,34 +1331,16 @@
},
{
"category": "gt",
"description": "should throw when calculating greater than with mixed data types",
"tests": [
{
"input": null,
"query": ["gt", "3", 2],
"throws": "Two numbers, strings, or booleans expected"
}
]
"description": "should return false when calculating greater than with mixed data types",
"tests": [{ "input": null, "query": ["gt", "3", 2], "output": false }]
},
{
"category": "gt",
"description": "should throw when calculating greater than with an unsupported data type",
"description": "should return false when calculating greater than with an unsupported data type",
"tests": [
{
"input": null,
"query": ["gt", 2, ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
},
{
"input": null,
"query": ["gt", ["array", 1, 2, 4], ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
},
{
"input": null,
"query": ["gt", 2, ["object", { "a": 1 }]],
"throws": "Two numbers, strings, or booleans expected"
}
{ "input": null, "query": ["gt", 2, ["array", 1, 2, 3]], "output": false },
{ "input": null, "query": ["gt", ["array", 1, 2, 4], ["array", 1, 2, 3]], "output": false },
{ "input": null, "query": ["gt", 2, ["object", { "a": 1 }]], "output": false }
]
},
{
Expand Down Expand Up @@ -1406,34 +1388,20 @@
},
{
"category": "gte",
"description": "should throw when calculating greater than or equal to with mixed data types",
"tests": [
{
"input": null,
"query": ["gte", "3", 2],
"throws": "Two numbers, strings, or booleans expected"
}
]
"description": "should return false when calculating greater than or equal to with mixed data types",
"tests": [{ "input": null, "query": ["gte", "3", 2], "output": false }]
},
{
"category": "gte",
"description": "should throw when calculating greater than or equal to with an unsupported data type",
"description": "should return false when calculating greater than or equal to with an unsupported data type",
"tests": [
{
"input": null,
"query": ["gte", 2, ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
},
{ "input": null, "query": ["gte", 2, ["array", 1, 2, 3]], "output": false },
{
"input": null,
"query": ["gte", ["array", 1, 2, 4], ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
"output": false
},
{
"input": null,
"query": ["gte", 2, ["object", { "a": 1 }]],
"throws": "Two numbers, strings, or booleans expected"
}
{ "input": null, "query": ["gte", 2, ["object", { "a": 1 }]], "output": false }
]
},
{
Expand Down Expand Up @@ -1481,34 +1449,16 @@
},
{
"category": "lt",
"description": "should throw when calculating less than with mixed data types",
"tests": [
{
"input": null,
"query": ["lt", 2, "3"],
"throws": "Two numbers, strings, or booleans expected"
}
]
"description": "should return false when calculating less than with mixed data types",
"tests": [{ "input": null, "query": ["lt", 2, "3"], "output": false }]
},
{
"category": "lt",
"description": "should throw when calculating less than with an unsupported data type",
"description": "should return false when calculating less than with an unsupported data type",
"tests": [
{
"input": null,
"query": ["lt", 2, ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
},
{
"input": null,
"query": ["lt", ["array", 1, 2, 4], ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
},
{
"input": null,
"query": ["lt", 2, ["object", { "a": 1 }]],
"throws": "Two numbers, strings, or booleans expected"
}
{ "input": null, "query": ["lt", 2, ["array", 1, 2, 3]], "output": false },
{ "input": null, "query": ["lt", ["array", 1, 2, 4], ["array", 1, 2, 3]], "output": false },
{ "input": null, "query": ["lt", 2, ["object", { "a": 1 }]], "output": false }
]
},
{
Expand Down Expand Up @@ -1560,34 +1510,20 @@
},
{
"category": "lte",
"description": "should throw when calculating less than or equal to with mixed data types",
"tests": [
{
"input": null,
"query": ["lte", "3", 2],
"throws": "Two numbers, strings, or booleans expected"
}
]
"description": "should return false when calculating less than or equal to with mixed data types",
"tests": [{ "input": null, "query": ["lte", "3", 2], "output": false }]
},
{
"category": "lte",
"description": "should throw when calculating less than or equal to with an unsupported data type",
"description": "should return false when calculating less than or equal to with an unsupported data type",
"tests": [
{
"input": null,
"query": ["lte", 2, ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
},
{ "input": null, "query": ["lte", 2, ["array", 1, 2, 3]], "output": false },
{
"input": null,
"query": ["lte", ["array", 1, 2, 4], ["array", 1, 2, 3]],
"throws": "Two numbers, strings, or booleans expected"
"output": false
},
{
"input": null,
"query": ["lte", 2, ["object", { "a": 1 }]],
"throws": "Two numbers, strings, or booleans expected"
}
{ "input": null, "query": ["lte", 2, ["object", { "a": 1 }]], "output": false }
]
},
{
Expand Down Expand Up @@ -1750,12 +1686,12 @@
},
{
"category": "and",
"description": "should throw when calculating and with no arguments",
"description": "should return null calculating and with no arguments",
"tests": [
{
"input": null,
"query": ["and"],
"throws": "Non-empty array expected"
"output": null
}
]
},
Expand Down Expand Up @@ -1821,12 +1757,12 @@
},
{
"category": "or",
"description": "should throw when calculating or with no arguments",
"description": "should return null when calculating or with no arguments",
"tests": [
{
"input": null,
"query": ["or"],
"throws": "Non-empty array expected"
"output": null
}
]
},
Expand Down