diff --git a/asyncLogic.js b/asyncLogic.js index b1e972b..c1b60d4 100644 --- a/asyncLogic.js +++ b/asyncLogic.js @@ -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 @@ -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.`) } /** diff --git a/compiler.js b/compiler.js index 0f929ff..1cc56dd 100644 --- a/compiler.js +++ b/compiler.js @@ -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 ( diff --git a/defaultMethods.js b/defaultMethods.js index 7d44965..479f3a0 100644 --- a/defaultMethods.js +++ b/defaultMethods.js @@ -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) diff --git a/general.test.js b/general.test.js new file mode 100644 index 0000000..73f1c3f --- /dev/null +++ b/general.test.js @@ -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 }) + } + }) +}) diff --git a/logic.js b/logic.js index 642f4ec..925828e 100644 --- a/logic.js +++ b/logic.js @@ -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 } } @@ -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.`) } /** diff --git a/package.json b/package.json index 25209e6..d351015 100644 --- a/package.json +++ b/package.json @@ -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",