From d5aade1e0148f59e14aa982186589de88be6c86b Mon Sep 17 00:00:00 2001 From: Jesse Mitchell Date: Sun, 8 Dec 2024 14:44:31 -0600 Subject: [PATCH 1/6] modified rfc6901 --- general.test.js | 11 +++++----- suites/scopes.json | 2 +- suites/vars.json | 48 +++++++++++++++++++++++++++++------------- utilities/splitPath.js | 25 +++++++++------------- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/general.test.js b/general.test.js index 7ecbb93..3418c39 100644 --- a/general.test.js +++ b/general.test.js @@ -123,11 +123,11 @@ describe('Various Test Cases', () => { }) it('is able to handle simple path escaping', async () => { - for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, 'b\\.c'] }, { selected: { 'b.c': 2 } }, 2) + for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, 'b~2c'] }, { selected: { 'b.c': 2 } }, 2) }) it('is able to handle simple path escaping in a variable', async () => { - for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, { var: 'key' }] }, { selected: { 'b.c': 2 }, key: 'b\\.c' }, 2) + for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, { var: 'key' }] }, { selected: { 'b.c': 2 }, key: 'b~2c' }, 2) }) it('is able to avoid returning functions', async () => { @@ -154,7 +154,7 @@ describe('Various Test Cases', () => { }) it('is able to handle path escaping in a var call', async () => { - for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: 'hello\\.world' }, { 'hello.world': 2 }, 2) + for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: 'hello~2world' }, { 'hello.world': 2 }, 2) }) it('is able to access empty keys', async () => { @@ -162,16 +162,15 @@ describe('Various Test Cases', () => { }) it('is able to access dot keys', async () => { - for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '\\.' }, { '.': 2 }, 2) + for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '~2' }, { '.': 2 }, 2) }) it('is able to access "/" keys from above', async () => { - for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { map: [[1], { '+': [{ var: '' }, { var: '../../..\\/' }] }] }, { '': { '': { '/': 3 } } }, [4]) + for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { map: [[1], { '+': [{ var: '' }, { var: '../../..~1' }] }] }, { '': { '': { '/': 3 } } }, [4]) }) it('is able to handle path escaping with multiple escapes', async () => { for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '\\foo' }, { '\\foo': 2 }, 2) - for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '\\\\foo' }, { '\\foo': 2 }, 2) }) it('is able to access the index in the iterators', async () => { diff --git a/suites/scopes.json b/suites/scopes.json index 1a56d89..381842c 100644 --- a/suites/scopes.json +++ b/suites/scopes.json @@ -58,7 +58,7 @@ "rule": { "map": [ { "var": "arr" }, - { "+": [{ "var": "../../\\.\\./" }, { "var": "../../..\\/" }]} + { "+": [{ "var": "../../~2~2~1" }, { "var": "../../..~1" }]} ] }, "data": { "arr": [1,2,3], "../": 10, "": { "": { "/": 7 }} }, diff --git a/suites/vars.json b/suites/vars.json index 06ba8be..f9743c8 100644 --- a/suites/vars.json +++ b/suites/vars.json @@ -20,62 +20,80 @@ }, { "description": "Fetches a value from a key that is purely a dot", - "rule": { "var": "\\." }, + "rule": { "var": "~2" }, "data": { "." : 20 }, "result": 20 }, { "description": "Fetches a value from a key with a dot in it", - "rule": { "var": "\\.key" }, + "rule": { "var": "~2key" }, "data": { ".key" : 4 }, "result": 4 }, { "description":"Fetches a value from a key with a dot in it (2)", - "rule": { "var": "hello\\.world" }, + "rule": { "var": "hello~2world" }, "data": { "hello.world" : 5 }, "result": 5 }, { "description": "Fetches a value from a key inside an empty key with a dot in it", - "rule": { "var": ".\\.key" }, + "rule": { "var": "/~2key" }, "data": { "": { ".key" : 6 } }, "result": 6 }, { "description": "Going a few levels deep", - "rule": { "var": "..\\.key." }, + "rule": { "var": "//~2key." }, "data": { "": { "": { ".key": { "": 7 }} }}, "result": 7 }, { "description": "Escape / as well, which is useful for the scope proposal", - "rule": { "var": "\\/" }, + "rule": { "var": "~1" }, "data": { "/" : 8 }, "result": 8 }, - { - "description": "Though / doesn't inherently need to be escaped", - "rule": { "var": "/" }, - "data": { "/" : 9 }, - "result": 9 - }, { "description": "Dot then empty key", - "rule": { "var": "\\.." }, + "rule": { "var": "~2." }, "data": { "." : { "" : 10 } }, "result": 10 }, { "description": "Empty key then dot", - "rule": { "var": ".\\." }, + "rule": { "var": ".~2" }, "data": { "" : { "." : 11 } }, "result": 11 }, { "description": "Can use backslack in name, too", - "rule": { "var": "\\\\.Hello" }, + "rule": { "var": "\\.Hello" }, "data": { "\\" : { "Hello" : 12 } }, "result": 12 + }, + { + "description": "Can escape tilde", + "rule": { "var": "~0" }, + "data": { "~" : 13 }, + "result": 13 + }, + { + "description": "Fetches a value from an empty key, traditional", + "rule": { "var": "/" }, + "data": { "" : 1 }, + "result": 1 + }, + { + "description": "Fetches a value from a nested empty key, traditional", + "rule": { "var": "//" }, + "data": { "" : { "": 2 } }, + "result": 2 + }, + { + "description": "Fetches a value from a doubly nested empty key, traditional", + "rule": { "var": "///" }, + "data": { "" : { "": { "": 3 } } }, + "result": 3 } ] \ No newline at end of file diff --git a/utilities/splitPath.js b/utilities/splitPath.js index c171c7b..de0597c 100644 --- a/utilities/splitPath.js +++ b/utilities/splitPath.js @@ -20,37 +20,32 @@ export function splitPathMemoized (str) { return parts } +const chars = ['~', '/', '.'] + /** * Splits a path string into an array of parts. * - * @example splitPath('a.b.c') // ['a', 'b', 'c'] - * @example splitPath('a\\.b.c') // ['a.b', 'c'] - * @example splitPath('a\\\\.b.c') // ['a\\', 'b', 'c'] - * @example splitPath('a\\\\\\.b.c') // ['a\\.b', 'c'] - * @example splitPath('hello') // ['hello'] - * @example splitPath('hello\\') // ['hello\\'] - * @example splitPath('hello\\\\') // ['hello\\'] * * @param {string} str * @param {string} separator * @returns {string[]} */ -export function splitPath (str, separator = '.', escape = '\\', up = '/') { +export function splitPath (str) { const parts = [] let current = '' for (let i = 0; i < str.length; i++) { const char = str[i] - if (char === escape) { - if (str[i + 1] === separator || str[i + 1] === up) { - current += str[i + 1] + if (char === '~') { + if (str[i + 1] === '0' || str[i + 1] === '1' || str[i + 1] === '2') { + current += chars[+str[i + 1]] i++ - } else if (str[i + 1] === escape) { - current += escape + } else if (str[i + 1] === '~') { + current += '~' i++ // The following else might be something tweaked in a spec. - } else current += escape - } else if (char === separator) { + } else throw new Error('Invalid escape sequence') + } else if (char === '.' || char === '/') { parts.push(current) current = '' } else current += char From 4ec11ff6b35ff0e1132cc08a2ef2b44cc8f47233 Mon Sep 17 00:00:00 2001 From: Jesse Mitchell Date: Sun, 8 Dec 2024 15:43:01 -0600 Subject: [PATCH 2/6] Add val to the proposal sutie --- defaultMethods.js | 14 ++++++++++++++ suites/val.json | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 suites/val.json diff --git a/defaultMethods.js b/defaultMethods.js index 0318587..f3b9669 100644 --- a/defaultMethods.js +++ b/defaultMethods.js @@ -203,6 +203,20 @@ const defaultMethods = { } } }, + // Adding this to spec something out, not to merge it. + val: { + method: (args, context) => { + let result = context + for (let i = 0; i < args.length; i++) { + if (args[i] === null) continue + if (result === null || result === undefined) return null + result = result[args[i]] + } + if (typeof result === 'undefined') return null + return result + }, + deterministic: false + }, var: (key, context, above, engine) => { let b if (Array.isArray(key)) { diff --git a/suites/val.json b/suites/val.json new file mode 100644 index 0000000..ae588a6 --- /dev/null +++ b/suites/val.json @@ -0,0 +1,39 @@ +[ + "Test Specification for val", + { + "description": "Fetches a value from an empty key", + "rule": { "val": "" }, + "data": { "" : 1 }, + "result": 1 + }, + { + "description": "Fetches a value from a nested empty key", + "rule": { "val": ["", ""] }, + "data": { "" : { "": 2 } }, + "result": 2 + }, + { + "description": "Fetches a value from a doubly nested empty key", + "rule": { "val": ["", "", ""] }, + "data": { "" : { "": { "": 3 } } }, + "result": 3 + }, + { + "description": "Fetches a value from a key that is purely a dot", + "rule": { "val": "." }, + "data": { "." : 20 }, + "result": 20 + }, + { + "description": "Fetches the entire context", + "rule": { "val": null }, + "data": { "": 21 }, + "result": { "": 21 } + }, + { + "description": "Fetches the entire context for a nested key", + "rule": { "val": "" }, + "data": { "": { "": 22 } }, + "result": { "": 22 } + } +] \ No newline at end of file From 905536473c429576db633e1f7ac290d6691f790d Mon Sep 17 00:00:00 2001 From: Jesse Mitchell Date: Sun, 8 Dec 2024 16:16:02 -0600 Subject: [PATCH 3/6] Commit this guy --- suites/val.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/suites/val.json b/suites/val.json index ae588a6..ed5969d 100644 --- a/suites/val.json +++ b/suites/val.json @@ -35,5 +35,17 @@ "rule": { "val": "" }, "data": { "": { "": 22 } }, "result": { "": 22 } + }, + { + "description": "Using val in a map (remember that null gets the current context, not empty string)", + "rule": { "map": [[1,2,3], { "+": [{ "val": null }, 1] }] }, + "data": null, + "result": [2,3,4] + }, + { + "description": "Using val in a map (and remember [null] and null are the same)", + "rule": { "map": [[1,2,3], { "+": [{ "val": [null] }, 1] }] }, + "data": null, + "result": [2,3,4] } ] \ No newline at end of file From 75365ec135f48917b947341f2a43edf4d4e7662d Mon Sep 17 00:00:00 2001 From: Jesse Mitchell Date: Sun, 8 Dec 2024 16:42:48 -0600 Subject: [PATCH 4/6] Retrofit val with scopes, as an example --- defaultMethods.js | 21 ++++++++++++++++++--- suites/val.json | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/defaultMethods.js b/defaultMethods.js index f3b9669..faaf130 100644 --- a/defaultMethods.js +++ b/defaultMethods.js @@ -205,9 +205,24 @@ const defaultMethods = { }, // Adding this to spec something out, not to merge it. val: { - method: (args, context) => { + method: (args, context, above) => { let result = context - for (let i = 0; i < args.length; i++) { + let start = 0 + if (Array.isArray(args[0]) && args[0].length === 1) { + start++ + const climb = +Math.abs(args[0][0]) + let pos = 0 + for (let i = 0; i < climb; i++) { + result = above[pos++] + if (i === above.length - 1 && Array.isArray(result)) { + above = result + result = result[0] + pos = 1 + } + } + } + + for (let i = start; i < args.length; i++) { if (args[i] === null) continue if (result === null || result === undefined) return null result = result[args[i]] @@ -553,7 +568,7 @@ function createArrayIterativeMethod (name, useTruthy = false) { } const method = build(mapper, mapState) - const aboveArray = method.aboveDetected ? buildState.compile`[{ item: null }, context, above]` : buildState.compile`null` + const aboveArray = method.aboveDetected ? buildState.compile`[{ item: null, index: x }, context, above]` : buildState.compile`null` if (async) { if (!isSyncDeep(mapper, buildState.engine, buildState)) { diff --git a/suites/val.json b/suites/val.json index ed5969d..73de36e 100644 --- a/suites/val.json +++ b/suites/val.json @@ -47,5 +47,26 @@ "rule": { "map": [[1,2,3], { "+": [{ "val": [null] }, 1] }] }, "data": null, "result": [2,3,4] + }, + "Testing out scopes", + { + "description": "Climb up to get adder", + "rule": { "map": [[1,2,3], { "+": [{ "val": null }, { "val": [[-2], "adder"] }] }] }, + "data": { "adder": 10 }, + "result": [11,12,13] + }, + { + "description": "Climb up to get index", + "rule": { "map": [[1,2,3], { "+": [{ "val": null }, { "val": [[-1], "index"] }] }] }, + "data": { "adder": 10 }, + "result": [1,3,5] + }, + { + "description": "Nested get adder", + "rule": { + "map": [["Test"], { "map": [[1,2,3], { "+": [{"val": null}, {"val": [[-4], "adder"]}] }]} ] + }, + "data": { "adder": 10 }, + "result": [[11,12,13]] } ] \ No newline at end of file From 9c6a048256690abf4b4d39205d6c0a4229eab761 Mon Sep 17 00:00:00 2001 From: Jesse Mitchell Date: Mon, 9 Dec 2024 10:58:38 -0600 Subject: [PATCH 5/6] Correct a small issue that made it not really RFC6901 Compliant --- general.test.js | 2 +- suites/scopes.json | 2 +- suites/vars.json | 24 ++++++++++++------------ utilities/splitPath.js | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/general.test.js b/general.test.js index 3418c39..e8fa44f 100644 --- a/general.test.js +++ b/general.test.js @@ -166,7 +166,7 @@ describe('Various Test Cases', () => { }) it('is able to access "/" keys from above', async () => { - for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { map: [[1], { '+': [{ var: '' }, { var: '../../..~1' }] }] }, { '': { '': { '/': 3 } } }, [4]) + for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { map: [[1], { '+': [{ var: '' }, { var: '../..////~1' }] }] }, { '': { '': { '/': 3 } } }, [4]) }) it('is able to handle path escaping with multiple escapes', async () => { diff --git a/suites/scopes.json b/suites/scopes.json index 381842c..5fb0cb8 100644 --- a/suites/scopes.json +++ b/suites/scopes.json @@ -58,7 +58,7 @@ "rule": { "map": [ { "var": "arr" }, - { "+": [{ "var": "../../~2~2~1" }, { "var": "../../..~1" }]} + { "+": [{ "var": "../../~2~2~1" }, { "var": "../..////~1" }]} ] }, "data": { "arr": [1,2,3], "../": 10, "": { "": { "/": 7 }} }, diff --git a/suites/vars.json b/suites/vars.json index f9743c8..bb47eb5 100644 --- a/suites/vars.json +++ b/suites/vars.json @@ -2,19 +2,19 @@ "Test Specification for Handling esoteric path traversal", { "description": "Fetches a value from an empty key", - "rule": { "var": "." }, + "rule": { "var": "/" }, "data": { "" : 1 }, "result": 1 }, { "description": "Fetches a value from a nested empty key", - "rule": { "var": ".." }, + "rule": { "var": "//" }, "data": { "" : { "": 2 } }, "result": 2 }, { "description": "Fetches a value from a doubly nested empty key", - "rule": { "var": "..." }, + "rule": { "var": "///" }, "data": { "" : { "": { "": 3 } } }, "result": 3 }, @@ -38,13 +38,13 @@ }, { "description": "Fetches a value from a key inside an empty key with a dot in it", - "rule": { "var": "/~2key" }, + "rule": { "var": "//~2key" }, "data": { "": { ".key" : 6 } }, "result": 6 }, { "description": "Going a few levels deep", - "rule": { "var": "//~2key." }, + "rule": { "var": "///~2key/" }, "data": { "": { "": { ".key": { "": 7 }} }}, "result": 7 }, @@ -62,7 +62,7 @@ }, { "description": "Empty key then dot", - "rule": { "var": ".~2" }, + "rule": { "var": "//~2" }, "data": { "" : { "." : 11 } }, "result": 11 }, @@ -79,20 +79,20 @@ "result": 13 }, { - "description": "Fetches a value from an empty key, traditional", - "rule": { "var": "/" }, + "description": "Fetches a value from an empty key, equivalence", + "rule": { "var": "." }, "data": { "" : 1 }, "result": 1 }, { - "description": "Fetches a value from a nested empty key, traditional", - "rule": { "var": "//" }, + "description": "Fetches a value from a nested empty key, equivalence", + "rule": { "var": ".." }, "data": { "" : { "": 2 } }, "result": 2 }, { - "description": "Fetches a value from a doubly nested empty key, traditional", - "rule": { "var": "///" }, + "description": "Fetches a value from a doubly nested empty key, equivalence", + "rule": { "var": "..." }, "data": { "" : { "": { "": 3 } } }, "result": 3 } diff --git a/utilities/splitPath.js b/utilities/splitPath.js index de0597c..1c21eed 100644 --- a/utilities/splitPath.js +++ b/utilities/splitPath.js @@ -46,7 +46,7 @@ export function splitPath (str) { // The following else might be something tweaked in a spec. } else throw new Error('Invalid escape sequence') } else if (char === '.' || char === '/') { - parts.push(current) + if (i) parts.push(current) current = '' } else current += char } From dccdc6b3c590fe0fc932c665a3a9f596c70bc089 Mon Sep 17 00:00:00 2001 From: Jesse Mitchell Date: Mon, 9 Dec 2024 11:01:07 -0600 Subject: [PATCH 6/6] Add two more tests for showcasing old format works --- suites/vars.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/suites/vars.json b/suites/vars.json index bb47eb5..41b1983 100644 --- a/suites/vars.json +++ b/suites/vars.json @@ -95,5 +95,17 @@ "rule": { "var": "..." }, "data": { "" : { "": { "": 3 } } }, "result": 3 + }, + { + "description": "Old format still works", + "rule": { "var": "hello.world" }, + "data": { "hello": { "world": 5 } }, + "result": 5 + }, + { + "description": "New format works too", + "rule": { "var": "hello/world" }, + "data": { "hello": { "world": 5 } }, + "result": 5 } ] \ No newline at end of file