Skip to content

Improve Permissive Implementation; Allows Data Detection Override #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 19, 2024
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
68 changes: 39 additions & 29 deletions asyncLogic.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@ class AsyncLogicEngine {
options = { yieldSupported: false, disableInline: false, permissive: false }
) {
this.methods = { ...methods }
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean}} */
this.options = { ...options }
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean }} */
this.options = { yieldSupported: options.yieldSupported, disableInline: options.disableInline }
this.disableInline = options.disableInline
this.async = true
this.fallback = new LogicEngine(methods, options)

if (!this.isData) {
if (!options.permissive) this.isData = () => false
else this.isData = (data, key) => !(key in this.methods)
}

this.fallback.isData = this.isData
}

/**
Expand All @@ -43,39 +50,42 @@ class AsyncLogicEngine {
async _parse (logic, context, above) {
const [func] = Object.keys(logic)
const data = logic[func]
if (this.methods[func]) {
if (typeof this.methods[func] === 'function') {
const input = await this.run(data, context, { above })
if (this.options.yieldSupported && (await checkYield(input))) {
return { result: input, func }
}
const result = await this.methods[func](input, context, above, this)
return { result: Array.isArray(result) ? Promise.all(result) : result, func }

if (this.isData(logic, func)) return { result: logic, func }

if (!this.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)

if (typeof this.methods[func] === 'function') {
const input = await this.run(data, context, { above })
if (this.options.yieldSupported && (await checkYield(input))) {
return { result: input, func }
}
const result = await this.methods[func](input, context, above, this)
return { result: Array.isArray(result) ? Promise.all(result) : result, func }
}

if (typeof this.methods[func] === 'object') {
const { asyncMethod, method, traverse } = this.methods[func]
const shouldTraverse =
if (typeof this.methods[func] === 'object') {
const { asyncMethod, method, traverse } = this.methods[func]
const shouldTraverse =
typeof traverse === 'undefined' ? true : traverse
const parsedData = shouldTraverse
? await this.run(data, context, { above })
: data
const parsedData = shouldTraverse
? await this.run(data, context, { above })
: data

if (this.options.yieldSupported && (await checkYield(parsedData))) {
return { result: parsedData, func }
}

const result = await (asyncMethod || method)(
parsedData,
context,
above,
this
)
return { result: Array.isArray(result) ? Promise.all(result) : result, func }
if (this.options.yieldSupported && (await checkYield(parsedData))) {
return { result: parsedData, func }
}

const result = await (asyncMethod || method)(
parsedData,
context,
above,
this
)
return { result: Array.isArray(result) ? Promise.all(result) : result, func }
}
if (this.options.permissive) return { result: logic, func }
throw new Error(`Method '${func}' was not found in the Logic Engine.`)

throw new Error(`Method '${func}' is not set up properly.`)
}

/**
Expand Down
7 changes: 5 additions & 2 deletions compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ function isDeterministic (method, engine, buildState) {
const func = Object.keys(method)[0]
const lower = method[func]

if (engine.isData(method, func)) return true
if (!engine.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)

if (engine.methods[func].traverse === false) {
return typeof engine.methods[func].deterministic === 'function'
? engine.methods[func].deterministic(lower, buildState)
Expand Down Expand Up @@ -274,8 +277,8 @@ function buildString (method, buildState = {}) {
if (method && typeof method === 'object') {
if (!func) return pushValue(method)
if (!engine.methods[func]) {
// If we are in permissive mode, we will just return the object.
if (engine.options.permissive) return pushValue(method, true)
// Check if this is supposed to be "data" rather than a function.
if (engine.isData(method, func)) return pushValue(method, true)
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
}
functions[func] = functions[func] || 2
Expand Down
45 changes: 45 additions & 0 deletions customEngines.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

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

class DataEngine extends LogicEngine {
isData (logic, firstKey) {
if (Object.keys(logic).length > 1) return true
return !(firstKey in this.methods)
}
}

class AsyncDataEngine extends AsyncLogicEngine {
isData (logic, firstKey) {
if (Object.keys(logic).length > 1) return true
return !(firstKey in this.methods)
}
}

const engine = new DataEngine()
const asyncEngine = new AsyncDataEngine()

describe('Custom Engines (isData)', () => {
const logic = {
get: [{
xs: 10,
s: 20,
m: 30
}, {
var: 'size'
}]
}

const data = {
size: 's'
}

it('Should let us override how data is detected (sync)', () => {
const f = engine.build(logic)
expect(f(data)).toEqual(20)
})

it('Should let us override how data is detected (async)', async () => {
const f = await asyncEngine.build(logic)
expect(await f(data)).toEqual(20)
})
})
3 changes: 2 additions & 1 deletion defaultMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ function isDeterministic (method, engine, buildState) {
const func = Object.keys(method)[0]
const lower = method[func]

if (!engine.methods[func] && engine.options.permissive) return true
if (engine.isData(method, func)) return true
if (!engine.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)

if (engine.methods[func].traverse === false) {
return typeof engine.methods[func].deterministic === 'function'
Expand Down
46 changes: 27 additions & 19 deletions logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ class LogicEngine {
) {
this.disableInline = options.disableInline
this.methods = { ...methods }
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean}} */
this.options = { ...options }
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean }} */
this.options = { yieldSupported: options.yieldSupported, disableInline: options.disableInline }
if (!this.isData) {
if (!options.permissive) this.isData = () => false
else this.isData = (data, key) => !(key in this.methods)
}
}

/**
Expand All @@ -38,24 +42,28 @@ class LogicEngine {
_parse (logic, context, above) {
const [func] = Object.keys(logic)
const data = logic[func]
if (this.methods[func]) {
if (typeof this.methods[func] === 'function') {
const input = this.run(data, context, { above })
if (this.options.yieldSupported && checkYield(input)) return { result: input, func }
return { result: this.methods[func](input, context, above, this), func }
}
if (typeof this.methods[func] === 'object') {
const { method, traverse } = this.methods[func]
const shouldTraverse = typeof traverse === 'undefined' ? true : traverse
const parsedData = shouldTraverse
? this.run(data, context, { above })
: data
if (this.options.yieldSupported && checkYield(parsedData)) return { result: parsedData, func }
return { result: method(parsedData, context, above, this), func }
}

if (this.isData(logic, func)) return { result: logic, func }

if (!this.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)

if (typeof this.methods[func] === 'function') {
const input = this.run(data, context, { above })
if (this.options.yieldSupported && checkYield(input)) return { result: input, func }
return { result: this.methods[func](input, context, above, this), func }
}

if (typeof this.methods[func] === 'object') {
const { method, traverse } = this.methods[func]
const shouldTraverse = typeof traverse === 'undefined' ? true : traverse
const parsedData = shouldTraverse
? this.run(data, context, { above })
: data
if (this.options.yieldSupported && checkYield(parsedData)) return { result: parsedData, func }
return { result: method(parsedData, context, above, this), func }
}
if (this.options.permissive) return { result: logic, func }
throw new Error(`Method '${func}' was not found in the Logic Engine.`)

throw new Error(`Method '${func}' is not set up properly.`)
}

/**
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.3.1",
"version": "1.3.2",
"description": "Construct complex rules with JSON & process them.",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down