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
87 changes: 49 additions & 38 deletions src/arrayHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,74 +14,85 @@
* limitations under the License.
*/

import type { RealTestFunc, TestOptions } from "./index"
import type { TestOptions, TestWithPathFunc } from "./index"

const fillHashtags = (count: number): string => "#".repeat(count)

/**
* Handles `$any`, `$all`, and `$inarray`. Works with nested loops!
* @param realTest The realTest function.
* @param testWithPath The testWithPath function.
* @param input The state machine.
* @param variables The variables.
* @param op The operation being performed.
* @param options The test options.
* @internal
*/
export function handleArrayLogic<Variables>(
realTest: RealTestFunc,
testWithPath: TestWithPathFunc,
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
})
throw new TypeError(
"Nested array nodes cannot use current iteration (`$.#`) as an `in` value",
{
cause: options._path,
},
)
}

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

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

for (const item of array) {
const test = realTest(itemConditions, variables, {
...options,
_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(depth)
const test = testWithPath(
itemConditions,
variables,
{
...options,
_currentLoopDepth: depth,
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(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.
if (
reference === `$.${hashtags}` ||
reference === `.${hashtags}`
) {
return item
}
// 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.
if (
reference === `$.${hashtags}` ||
reference === `.${hashtags}`
) {
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
}
// 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)
}
})
return options.findNamedChild(reference, variables)
},
},
`${op}.?`,
)

if (test && (op === "$inarray" || op === "$any")) {
return true
Expand Down
60 changes: 43 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,41 @@ export function test<Context = Record<string, unknown>>(

const opts = options || {}

return realTest(input, context, {
findNamedChild: opts.findNamedChild || findNamedChild,
...opts,
_path: opts._path || "ROOTOBJ",
_currentLoopDepth: 0,
logger: opts.logger || (() => {}),
})
return testWithPath(
input,
context,
{
findNamedChild: opts.findNamedChild || findNamedChild,
...opts,
_path: "",
_currentLoopDepth: 0,
logger: opts.logger || (() => {}),
},
"",
)
}

/**
* Tiny wrapper function that calls {@link realTest} with a path specified.
* The benefit of using this is that it's a single, inline call, instead of 4
* lines per call.
*/
function testWithPath(input: any, context, options: TestOptions, name: string) {
return realTest(input, context, {
function testWithPath<Context>(
input: any,
context: Context,
options: TestOptions,
name: string,
) {
// the top-most call
const thePath = options._path ? `${options._path}.${name}` : name
const displayPath = thePath || "(root)"
options.logger?.("visit", `Visiting ${displayPath}`)
const result = realTest(input, context, {
...options,
_path: `${options._path}.${name}`,
_path: thePath,
})
options.logger?.("trace", `${displayPath} evaluated to: ${result}`)
return result
}

function realTest<Variables>(
Expand All @@ -74,8 +90,6 @@ function realTest<Variables>(
): Variables | boolean {
const log = options.logger

log("visit", `Visiting ${options._path}`)

if (
typeof input === "number" ||
typeof input === "boolean" ||
Expand Down Expand Up @@ -169,7 +183,7 @@ function realTest<Variables>(

if (input.$inarray) {
return handleArrayLogic(
realTest,
testWithPath,
input,
variables,
"$inarray",
Expand All @@ -178,11 +192,23 @@ function realTest<Variables>(
}

if (input.$any) {
return handleArrayLogic(realTest, input, variables, "$any", options)
return handleArrayLogic(
testWithPath,
input,
variables,
"$any",
options,
)
}

if (input.$all) {
return handleArrayLogic(realTest, input, variables, "$all", options)
return handleArrayLogic(
testWithPath,
input,
variables,
"$all",
options,
)
}

if (input.$after) {
Expand Down Expand Up @@ -264,7 +290,7 @@ function realTest<Variables>(
)

if (typeof first === "string") {
return first.includes(second)
return first.includes(second as string)
}

return false
Expand All @@ -279,7 +305,7 @@ function realTest<Variables>(
/**
* @internal
*/
export type RealTestFunc = typeof realTest
export type TestWithPathFunc = typeof testWithPath

/**
* Handles a group of action nodes (a.k.a. side-effect nodes).
Expand Down
Loading