Skip to content

Commit 4fcbe2e

Browse files
Merge pull request #29 from TotalTechGeek/feat/customize-data-detection
Improve Permissive Implementation; Allows Data Detection Override
2 parents b6a4164 + 77343ab commit 4fcbe2e

File tree

6 files changed

+119
-52
lines changed

6 files changed

+119
-52
lines changed

asyncLogic.js

+39-29
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,18 @@ class AsyncLogicEngine {
2626
options = { yieldSupported: false, disableInline: false, permissive: false }
2727
) {
2828
this.methods = { ...methods }
29-
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean}} */
30-
this.options = { ...options }
29+
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean }} */
30+
this.options = { yieldSupported: options.yieldSupported, disableInline: options.disableInline }
3131
this.disableInline = options.disableInline
3232
this.async = true
3333
this.fallback = new LogicEngine(methods, options)
34+
35+
if (!this.isData) {
36+
if (!options.permissive) this.isData = () => false
37+
else this.isData = (data, key) => !(key in this.methods)
38+
}
39+
40+
this.fallback.isData = this.isData
3441
}
3542

3643
/**
@@ -43,39 +50,42 @@ class AsyncLogicEngine {
4350
async _parse (logic, context, above) {
4451
const [func] = Object.keys(logic)
4552
const data = logic[func]
46-
if (this.methods[func]) {
47-
if (typeof this.methods[func] === 'function') {
48-
const input = await this.run(data, context, { above })
49-
if (this.options.yieldSupported && (await checkYield(input))) {
50-
return { result: input, func }
51-
}
52-
const result = await this.methods[func](input, context, above, this)
53-
return { result: Array.isArray(result) ? Promise.all(result) : result, func }
53+
54+
if (this.isData(logic, func)) return { result: logic, func }
55+
56+
if (!this.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)
57+
58+
if (typeof this.methods[func] === 'function') {
59+
const input = await this.run(data, context, { above })
60+
if (this.options.yieldSupported && (await checkYield(input))) {
61+
return { result: input, func }
5462
}
63+
const result = await this.methods[func](input, context, above, this)
64+
return { result: Array.isArray(result) ? Promise.all(result) : result, func }
65+
}
5566

56-
if (typeof this.methods[func] === 'object') {
57-
const { asyncMethod, method, traverse } = this.methods[func]
58-
const shouldTraverse =
67+
if (typeof this.methods[func] === 'object') {
68+
const { asyncMethod, method, traverse } = this.methods[func]
69+
const shouldTraverse =
5970
typeof traverse === 'undefined' ? true : traverse
60-
const parsedData = shouldTraverse
61-
? await this.run(data, context, { above })
62-
: data
71+
const parsedData = shouldTraverse
72+
? await this.run(data, context, { above })
73+
: data
6374

64-
if (this.options.yieldSupported && (await checkYield(parsedData))) {
65-
return { result: parsedData, func }
66-
}
67-
68-
const result = await (asyncMethod || method)(
69-
parsedData,
70-
context,
71-
above,
72-
this
73-
)
74-
return { result: Array.isArray(result) ? Promise.all(result) : result, func }
75+
if (this.options.yieldSupported && (await checkYield(parsedData))) {
76+
return { result: parsedData, func }
7577
}
78+
79+
const result = await (asyncMethod || method)(
80+
parsedData,
81+
context,
82+
above,
83+
this
84+
)
85+
return { result: Array.isArray(result) ? Promise.all(result) : result, func }
7686
}
77-
if (this.options.permissive) return { result: logic, func }
78-
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
87+
88+
throw new Error(`Method '${func}' is not set up properly.`)
7989
}
8090

8191
/**

compiler.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ function isDeterministic (method, engine, buildState) {
6262
const func = Object.keys(method)[0]
6363
const lower = method[func]
6464

65+
if (engine.isData(method, func)) return true
66+
if (!engine.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)
67+
6568
if (engine.methods[func].traverse === false) {
6669
return typeof engine.methods[func].deterministic === 'function'
6770
? engine.methods[func].deterministic(lower, buildState)
@@ -274,8 +277,8 @@ function buildString (method, buildState = {}) {
274277
if (method && typeof method === 'object') {
275278
if (!func) return pushValue(method)
276279
if (!engine.methods[func]) {
277-
// If we are in permissive mode, we will just return the object.
278-
if (engine.options.permissive) return pushValue(method, true)
280+
// Check if this is supposed to be "data" rather than a function.
281+
if (engine.isData(method, func)) return pushValue(method, true)
279282
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
280283
}
281284
functions[func] = functions[func] || 2

customEngines.test.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
import { LogicEngine, AsyncLogicEngine } from './index.js'
3+
4+
class DataEngine extends LogicEngine {
5+
isData (logic, firstKey) {
6+
if (Object.keys(logic).length > 1) return true
7+
return !(firstKey in this.methods)
8+
}
9+
}
10+
11+
class AsyncDataEngine extends AsyncLogicEngine {
12+
isData (logic, firstKey) {
13+
if (Object.keys(logic).length > 1) return true
14+
return !(firstKey in this.methods)
15+
}
16+
}
17+
18+
const engine = new DataEngine()
19+
const asyncEngine = new AsyncDataEngine()
20+
21+
describe('Custom Engines (isData)', () => {
22+
const logic = {
23+
get: [{
24+
xs: 10,
25+
s: 20,
26+
m: 30
27+
}, {
28+
var: 'size'
29+
}]
30+
}
31+
32+
const data = {
33+
size: 's'
34+
}
35+
36+
it('Should let us override how data is detected (sync)', () => {
37+
const f = engine.build(logic)
38+
expect(f(data)).toEqual(20)
39+
})
40+
41+
it('Should let us override how data is detected (async)', async () => {
42+
const f = await asyncEngine.build(logic)
43+
expect(await f(data)).toEqual(20)
44+
})
45+
})

defaultMethods.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ function isDeterministic (method, engine, buildState) {
1818
const func = Object.keys(method)[0]
1919
const lower = method[func]
2020

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

2324
if (engine.methods[func].traverse === false) {
2425
return typeof engine.methods[func].deterministic === 'function'

logic.js

+27-19
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ class LogicEngine {
2424
) {
2525
this.disableInline = options.disableInline
2626
this.methods = { ...methods }
27-
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean, permissive?: boolean}} */
28-
this.options = { ...options }
27+
/** @type {{yieldSupported?: Boolean, disableInline?: Boolean }} */
28+
this.options = { yieldSupported: options.yieldSupported, disableInline: options.disableInline }
29+
if (!this.isData) {
30+
if (!options.permissive) this.isData = () => false
31+
else this.isData = (data, key) => !(key in this.methods)
32+
}
2933
}
3034

3135
/**
@@ -38,24 +42,28 @@ class LogicEngine {
3842
_parse (logic, context, above) {
3943
const [func] = Object.keys(logic)
4044
const data = logic[func]
41-
if (this.methods[func]) {
42-
if (typeof this.methods[func] === 'function') {
43-
const input = this.run(data, context, { above })
44-
if (this.options.yieldSupported && checkYield(input)) return { result: input, func }
45-
return { result: this.methods[func](input, context, above, this), func }
46-
}
47-
if (typeof this.methods[func] === 'object') {
48-
const { method, traverse } = this.methods[func]
49-
const shouldTraverse = typeof traverse === 'undefined' ? true : traverse
50-
const parsedData = shouldTraverse
51-
? this.run(data, context, { above })
52-
: data
53-
if (this.options.yieldSupported && checkYield(parsedData)) return { result: parsedData, func }
54-
return { result: method(parsedData, context, above, this), func }
55-
}
45+
46+
if (this.isData(logic, func)) return { result: logic, func }
47+
48+
if (!this.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)
49+
50+
if (typeof this.methods[func] === 'function') {
51+
const input = this.run(data, context, { above })
52+
if (this.options.yieldSupported && checkYield(input)) return { result: input, func }
53+
return { result: this.methods[func](input, context, above, this), func }
54+
}
55+
56+
if (typeof this.methods[func] === 'object') {
57+
const { method, traverse } = this.methods[func]
58+
const shouldTraverse = typeof traverse === 'undefined' ? true : traverse
59+
const parsedData = shouldTraverse
60+
? this.run(data, context, { above })
61+
: data
62+
if (this.options.yieldSupported && checkYield(parsedData)) return { result: parsedData, func }
63+
return { result: method(parsedData, context, above, this), func }
5664
}
57-
if (this.options.permissive) return { result: logic, func }
58-
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
65+
66+
throw new Error(`Method '${func}' is not set up properly.`)
5967
}
6068

6169
/**

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-logic-engine",
3-
"version": "1.3.1",
3+
"version": "1.3.2",
44
"description": "Construct complex rules with JSON & process them.",
55
"main": "./dist/cjs/index.js",
66
"module": "./dist/esm/index.js",

0 commit comments

Comments
 (0)