Skip to content

Commit

Permalink
feat: Add handling of arrays of objects (#30)
Browse files Browse the repository at this point in the history
* Add handling of arrays of objects

* feat: throw an error if invalid input is provided

---------

Co-authored-by: Reece Dunham <me@rdil.rocks>
  • Loading branch information
AnthonyFuller and RDIL authored Jul 25, 2024
1 parent ca8402e commit 2b24a25
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 17 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"build": "tsc && node build.mjs",
"prepack": "yarn build",
"test": "mocha --require esbuild-register --extension js,ts,cjs,mjs tests",
"coverage": "c8 --reporter=lcov --reporter=text-summary yarn test"
"coverage": "c8 --reporter=lcov --reporter=text-summary --exclude=.yarn yarn test"
},
"prettier": {
"semi": false,
Expand Down
43 changes: 29 additions & 14 deletions src/arrayHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,47 @@ import type { RealTestFunc, TestOptions } from "./index"
const fillHashtags = (count: number): string => "#".repeat(count)

/**
* Function that creates an array-like test node parser.
* It's split into a separate file for the sake of organization, and uses the proxy function to avoid circular dependencies.
*
* @param realTest The realTest function (internal).
* Handles `$any`, `$all`, and `$inarray`. Works with nested loops!
* @param realTest The realTest function.
* @param input The state machine.
* @param variables The variables.
* @param op The operation being performed.
* @param options The test option.
* @param options The test options.
* @internal
*/
export function handleArrayLogic<Variables>(
realTest: RealTestFunc,
input: any,
variables: Variables,
op: string,
options: TestOptions,
options: TestOptions
): boolean {
const inValue = input[op]["in"]
const depth = (options._currentLoopDepth || 0) + 1

if (inValue.includes("#")) {
throw new TypeError("Nested array nodes cannot use current iteration (`$.#`) as an `in` value", {
cause: options._path
})
}

// find the array
const array = realTest(input[op]["in"], variables, {
const array = realTest(inValue, variables, {
...options,
_currentLoopDepth: (options._currentLoopDepth || 0) + 1,
_path: `${options._path}.${op}.in`,
_currentLoopDepth: depth,
_path: `${options._path}.${op}.in`
}) as unknown as unknown[]

const itemConditions = input[op]["?"]

for (const item of array) {
const test = realTest(itemConditions, variables, {
...options,
_currentLoopDepth: (options._currentLoopDepth || 0) + 1,
_currentLoopDepth: depth,
_path: `${options._path}.${op}.?`,
findNamedChild(reference, variables) {
// NOTE: if we have a multi-layered loop, this should one-by-one fall back until the targeted loop is hit
const hashtags = fillHashtags(
(options._currentLoopDepth || 0) + 1,
)
const hashtags = fillHashtags(depth)

// a little future-proofing, as sometimes the $ is there, and other times it isn't.
// we strip it out somewhere, but it shouldn't matter too much.
Expand All @@ -64,8 +70,17 @@ export function handleArrayLogic<Variables>(
return item
}

// handle properties of an object
if (typeof item === "object") {
const newReference = `$${reference.substring(
reference.indexOf("#.") + 1
)}`
const found = options.findNamedChild(newReference, item)
if (found !== newReference) return found
}

return options.findNamedChild(reference, variables)
},
}
})

if (test && (op === "$inarray" || op === "$any")) {
Expand Down
81 changes: 81 additions & 0 deletions tests/inarray.data.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,86 @@
]
}
}
],
"Any3": [
{
"$any": {
"in": "$Value.listvar",
"?": {
"$eq": [
"$.#.prop",
6
]
}
}
},
{
"Value": {
"listvar": [
{
"prop": 4
},
{
"prop": 6
}
]
}
}
],
"Any4": [
{
"$any": {
"in": "$Value.listvar",
"?": {
"$eq": [
"$.#.prop",
5
]
}
}
},
{
"Value": {
"listvar": [
{
"prop": 4
},
{
"prop": 6
}
]
}
}
],
"Invalid_Crash_Nested": [
{
"$any": {
"in": "$.MyList",
"?": {
"$any": {
"in": "$.#",
"?": {
"$eq": ["$.##.prop", 4]
}
}
}
}
},
{
"Value": {
"MyList": [
[
{
"prop": 4
}
],
[
{
"prop": 6
}
]
]
}
}
]
}
23 changes: 21 additions & 2 deletions tests/inarray.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,31 @@ describe("$inarray", () => {

describe("$any", () => {
it("can find a string in a context array", () => {
const [sm, vars] = data.Inarray1
const [sm, vars] = data.Any1
assert.strictEqual(test(sm, vars), true)
})

it("returns false if the item isn't present", () => {
const [sm, vars] = data.Inarray2
const [sm, vars] = data.Any2
assert.strictEqual(test(sm, vars), false)
})

it("can find a property in a context array", () => {
const [sm, vars] = data.Any3
assert.strictEqual(test(sm, vars), true)
})

it("returns false if a property isn't present", () => {
const [sm, vars] = data.Any4
assert.strictEqual(test(sm, vars), false)
})

describe("nested", () => {
it("throws when trying to use current iteration as an `in` value", () => {
const [sm, vars] = data.Invalid_Crash_Nested
assert.throws(() => {
test(sm, vars)
}, /Nested array nodes cannot use current iteration \(`\$.#`\) as an `in` value/)
})
})
})

0 comments on commit 2b24a25

Please sign in to comment.