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
7 changes: 5 additions & 2 deletions asyncLogic.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ class AsyncLogicEngine {
/**
*
* @param {Object} methods An object that stores key-value pairs between the names of the commands & the functions they execute.
* @param {{ yieldSupported?: Boolean, disableInline?: Boolean }} options
* @param {{ yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean }} options
*/
constructor (
methods = defaultMethods,
options = { yieldSupported: false, disableInline: false }
options = { yieldSupported: false, disableInline: false, permissive: false }
) {
this.methods = { ...methods }
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean}} */
this.options = { ...options }
this.disableInline = options.disableInline
this.async = true
Expand Down Expand Up @@ -73,6 +74,8 @@ class AsyncLogicEngine {
return Array.isArray(result) ? Promise.all(result) : result
}
}
if (this.options.permissive) return { [func]: data }
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
}

/**
Expand Down
6 changes: 5 additions & 1 deletion compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,11 @@ function buildString (method, buildState = {}) {
buildState.useContext || (engine.methods[func] || {}).useContext

if (method && typeof method === 'object') {
if (!engine.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)
if (!engine.methods[func]) {
// If we are in permissive mode, we will just return the object.
if (engine.options.permissive) return pushValue(method)
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
}
functions[func] = functions[func] || 2

if (
Expand Down
3 changes: 3 additions & 0 deletions defaultMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ function isDeterministic (method, engine, buildState) {
if (method && typeof method === 'object') {
const func = Object.keys(method)[0]
const lower = method[func]

if (!engine.methods[func] && engine.options.permissive) return true

if (engine.methods[func].traverse === false) {
return typeof engine.methods[func].deterministic === 'function'
? engine.methods[func].deterministic(lower, buildState)
Expand Down
89 changes: 89 additions & 0 deletions general.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@

import assert from 'assert'
import { LogicEngine, AsyncLogicEngine } from './index.js'

const normalEngines = [
new LogicEngine(),
new AsyncLogicEngine(),
new LogicEngine(undefined, { yieldSupported: true }),
new AsyncLogicEngine(undefined, { yieldSupported: true })
]

const permissiveEngines = [
new LogicEngine(undefined, { permissive: true }),
new AsyncLogicEngine(undefined, { permissive: true }),
new LogicEngine(undefined, { yieldSupported: true, permissive: true }),
new AsyncLogicEngine(undefined, { yieldSupported: true, permissive: true })
]

async function testEngineAsync (engine, rule, data, expected) {
// run
if (expected === Error) {
try {
await engine.run(rule, data)
throw new Error('Should have failed')
} catch (e) {}
} else {
const result = await engine.run(rule, data)
assert.deepStrictEqual(result, expected)
}

// build
if (expected === Error) {
try {
const built = await engine.build(rule)
await built(data)
throw new Error('Should have failed')
} catch (e) {}
} else {
const built = await engine.build(rule)
const builtResult = await built(data)
assert.deepStrictEqual(builtResult, expected)
}
}

function testEngine (engine, rule, data, expected) {
if (engine instanceof AsyncLogicEngine) {
return testEngineAsync(engine, rule, data, expected)
}

// run
if (expected === Error) {
try {
engine.run(rule, data)
throw new Error('Should have failed')
} catch (e) {}
} else {
const result = engine.run(rule, data)
assert.deepStrictEqual(result, expected)
}

// build
if (expected === Error) {
try {
const built = engine.build(rule)
built(data)
throw new Error('Should have failed')
} catch (e) {}
} else {
const built = engine.build(rule)
const builtResult = built(data)
assert.deepStrictEqual(builtResult, expected)
}
}

describe('Various Test Cases', () => {
it('Should fail when an unrecognized method is used.', async () => {
for (const engine of normalEngines) await testEngine(engine, { unknown: true }, {}, Error)
})

it('Should return the object when an unrecognized method is used.', async () => {
for (const engine of permissiveEngines) {
await testEngine(engine, { unknown: true }, {}, { unknown: true })

await testEngine(engine, {
if: [true, { unknown: true }, 5]
}, {}, { unknown: true })
}
})
})
14 changes: 7 additions & 7 deletions logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ class LogicEngine {
/**
*
* @param {Object} methods An object that stores key-value pairs between the names of the commands & the functions they execute.
* @param {{ yieldSupported?: Boolean, disableInline?: Boolean }} options
* @param {{ yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean }} options
*/
constructor (
methods = defaultMethods,
options = { yieldSupported: false, disableInline: false }
options = { yieldSupported: false, disableInline: false, permissive: false }
) {
this.disableInline = options.disableInline
this.methods = { ...methods }
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean}} */
this.options = { ...options }
}

Expand All @@ -44,17 +45,16 @@ class LogicEngine {
}
if (typeof this.methods[func] === 'object') {
const { method, traverse } = this.methods[func]
const shouldTraverse =
typeof traverse === 'undefined' ? true : traverse
const shouldTraverse = typeof traverse === 'undefined' ? true : traverse
const parsedData = shouldTraverse
? this.run(data, context, { above })
: data
if (this.options.yieldSupported && checkYield(parsedData)) {
return parsedData
}
if (this.options.yieldSupported && checkYield(parsedData)) return parsedData
return method(parsedData, context, above, this)
}
}
if (this.options.permissive) return { [func]: data }
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
}

/**
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "json-logic-engine",
"version": "1.2.0",
"version": "1.2.1",
"description": "Construct complex rules with JSON & process them.",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down