Skip to content

Commit

Permalink
feat: introduce tracing (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
RDIL authored Nov 20, 2024
1 parent aac3f95 commit 8d3fa09
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 55 deletions.
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

0 comments on commit 8d3fa09

Please sign in to comment.