From e90dd26a1346fcf61df614a587ec4ce9113f613b Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Fri, 22 Mar 2024 16:07:22 +0000 Subject: [PATCH 1/6] [Path] Add absolute constant and predicate --- src/path/constants.js | 1 + src/path/index.js | 2 ++ src/path/predicates.js | 9 +++++++++ 3 files changed, 12 insertions(+) create mode 100644 src/path/constants.js create mode 100644 src/path/index.js create mode 100644 src/path/predicates.js diff --git a/src/path/constants.js b/src/path/constants.js new file mode 100644 index 0000000..9420ada --- /dev/null +++ b/src/path/constants.js @@ -0,0 +1 @@ +export const ABSOLUTE_START = '$.'; diff --git a/src/path/index.js b/src/path/index.js new file mode 100644 index 0000000..3b98b2a --- /dev/null +++ b/src/path/index.js @@ -0,0 +1,2 @@ +export * from './constants.js'; +export * from './predicates.js'; diff --git a/src/path/predicates.js b/src/path/predicates.js new file mode 100644 index 0000000..3581160 --- /dev/null +++ b/src/path/predicates.js @@ -0,0 +1,9 @@ +import isMetadata from '../metadata/is-metadata.js'; +import {ABSOLUTE_START} from './constants.js'; + +export const isAbsolute = (path) => path.slice(0,2) === ABSOLUTE_START; + +export { + // Reexport `isMetadata()` for convenience + isMetadata, +}; From 67b4f7272851bf91732f1878d1c041894a0150be Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Fri, 22 Mar 2024 16:14:28 +0000 Subject: [PATCH 2/6] [Record] Add support for absolute path --- src/record/extract-collection-item.js | 4 +++- src/record/extract-collection-item.test.js | 8 ++++++++ src/record/extract-collection-member.js | 4 +++- src/record/extract-collection-member.test.js | 6 ++++++ src/record/extract-member.js | 4 +++- src/record/extract-member.test.js | 7 +++++++ src/record/extract-value.js | 3 ++- src/record/extract-value.test.js | 13 +++++++++++++ 8 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/record/extract-collection-item.js b/src/record/extract-collection-item.js index 7bfba53..eb279f3 100644 --- a/src/record/extract-collection-item.js +++ b/src/record/extract-collection-item.js @@ -1,3 +1,5 @@ +import {isAbsolute, isMetadata} from '../path/index.js'; + /** * Creates an instance of a relative value extractor for items of a collection field. * This can be further combined with `extractMember` to extract members of complex collection items. @@ -16,7 +18,7 @@ const resolvePath = (parentPath) => (path) => { return `${parentPath}[${path}].value`; } - if (path[0] === '[') { + if (isMetadata(path) || isAbsolute(path)) { return path; } diff --git a/src/record/extract-collection-item.test.js b/src/record/extract-collection-item.test.js index 8d9df9a..0834137 100644 --- a/src/record/extract-collection-item.test.js +++ b/src/record/extract-collection-item.test.js @@ -1,9 +1,11 @@ +import extractCollectionMember from './extract-collection-member.js'; import extractValue from './extract-value.js'; import extractCollectionItem from './extract-collection-item.js'; const extractor = extractValue({ id: '1111222233334444', data: { + 'field0': 'Root value 0', 'collection1': [ {id: 'item-1', value: 'Value 1'}, {id: 'item-2', value: 'Value 2'}, @@ -30,6 +32,12 @@ describe('when single item path', () => { extractCollectionItem(extractor, 'collection1')('[id]') ).toEqual('1111222233334444'); }); + + test('should extract absolute path from root', () => { + expect( + extractCollectionItem(extractor, 'collection1')('$.field0') + ).toEqual('Root value 0'); + }); }); describe('when array of item paths', () => { diff --git a/src/record/extract-collection-member.js b/src/record/extract-collection-member.js index b8c6fb8..b1020d6 100644 --- a/src/record/extract-collection-member.js +++ b/src/record/extract-collection-member.js @@ -1,3 +1,5 @@ +import {isAbsolute, isMetadata} from '../path/index.js'; + /** * Creates an instance of a relative value extractor for members of a complex collection field. * @@ -11,7 +13,7 @@ const extractCollectionMember = (extract, collectionPath) => (item) => (path) => }; const resolvePath = (parentPath) => (item) => (path) => { - if (path[0] === '[') { + if (isMetadata(path) || isAbsolute(path)) { return path; } diff --git a/src/record/extract-collection-member.test.js b/src/record/extract-collection-member.test.js index 5172e8e..3f9984d 100644 --- a/src/record/extract-collection-member.test.js +++ b/src/record/extract-collection-member.test.js @@ -4,6 +4,7 @@ import extractValue from './extract-value.js'; const extractor = extractValue({ id: '1111222233334444', data: { + 'field0': 'Root value 0', 'collection1': [ { id: 'item-1', @@ -45,6 +46,11 @@ describe('when single item path', () => { const extract = extractCollectionMember(extractor, 'collection1')('item-2'); expect(extract('[id]')).toEqual('1111222233334444'); }); + + test('should extract absolute path from root', () => { + const extract = extractCollectionMember(extractor, 'collection1')('item-2'); + expect(extract('$.field0')).toEqual('Root value 0'); + }); }); describe('when array of item paths', () => { diff --git a/src/record/extract-member.js b/src/record/extract-member.js index 5a36671..ff91b44 100644 --- a/src/record/extract-member.js +++ b/src/record/extract-member.js @@ -1,3 +1,5 @@ +import {isAbsolute, isMetadata} from '../path/index.js'; + /** * Creates an instance of a relative value extractor for members of a complex field. * @@ -11,7 +13,7 @@ const extractMember = (extract, complexPath) => (path) => { }; const resolvePath = (parentPath) => (path) => { - if (path[0] === '[') { + if (isMetadata(path) || isAbsolute(path)) { return path; } return parentPath + '.' + path; diff --git a/src/record/extract-member.test.js b/src/record/extract-member.test.js index d83376c..092076e 100644 --- a/src/record/extract-member.test.js +++ b/src/record/extract-member.test.js @@ -4,6 +4,7 @@ import extractMember from './extract-member.js'; const extractor = extractValue({ id: '1111222233334444', data: { + 'field0': 'Root value 0', 'complex1': { 'member1': 'Value 1', 'member2': 'Value 2', @@ -24,6 +25,12 @@ describe('when single member path', () => { extractMember(extractor, 'complex1')('[id]') ).toEqual('1111222233334444'); }); + + test('should extract absolute path from root', () => { + expect( + extractMember(extractor, 'complex1')('$.field0') + ).toEqual('Root value 0'); + }); }); describe('when array of member paths', () => { diff --git a/src/record/extract-value.js b/src/record/extract-value.js index 9b52d35..e561c5d 100644 --- a/src/record/extract-value.js +++ b/src/record/extract-value.js @@ -1,5 +1,6 @@ import {isMetadata} from '../metadata/index.js'; import * as Metadata from '../metadata/index.js'; +import * as Path from '../path/index.js'; import comboExtractor from '../path/combo-extractor.js'; const COLLECTION_ITEM_PATTERN = /^(?[^[\]]+)(?:\[(?:(?\d+)|id:(?[^[\]]+))\])?$/; @@ -22,7 +23,7 @@ const valueExtractor = (record) => (path) => { } const caseData = dataExtractor(record); - return caseData ? field(caseData)(path.split('.').map(parsePathElement)) : undefined; + return caseData ? field(caseData)(path.replace(Path.ABSOLUTE_START, '').split('.').map(parsePathElement)) : undefined; }; const metadataExtractor = (record) => (path) => { diff --git a/src/record/extract-value.test.js b/src/record/extract-value.test.js index caa2471..93db4d5 100644 --- a/src/record/extract-value.test.js +++ b/src/record/extract-value.test.js @@ -38,6 +38,19 @@ test('should extract field from record `case_data`', () => { expect(fieldValue).toEqual('value'); }); +test('should extract field using explicit absolute path', () => { + const record = { + data: { + level1: { + level2: 'value' + } + } + }; + + const fieldValue = extractValue(record)('$.level1.level2'); + expect(fieldValue).toEqual('value'); +}); + test('should extract field as undefined when path does not exist', () => { const record = { data: { From 1323725fb1e57818b0fa383998340668bf824c13 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Fri, 22 Mar 2024 16:19:36 +0000 Subject: [PATCH 3/6] [Condition] Add support for absolute path --- src/condition/extract-tokens.js | 8 ++++---- src/condition/extract-tokens.test.js | 6 +++--- src/condition/parse-tokens.js | 2 +- src/condition/parse-tokens.test.js | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/condition/extract-tokens.js b/src/condition/extract-tokens.js index 366cc28..c97fdfa 100644 --- a/src/condition/extract-tokens.js +++ b/src/condition/extract-tokens.js @@ -14,8 +14,8 @@ const LOWER = Object.freeze({ }); const SYMBOLS = Object.freeze({ - AT: 64, COLON: 58, + DOLLAR: 36, DOUBLE_QUOTE: 34, PARENTHESIS_OPEN: 40, PARENTHESIS_CLOSE: 41, @@ -36,7 +36,7 @@ const GROUP_DELIMITERS = [ ]; const FIELD_PATH_SYMBOLS = [ - SYMBOLS.AT, + SYMBOLS.DOLLAR, SYMBOLS.COLON, SYMBOLS.DOT, SYMBOLS.SQUARE_BRACKET_OPEN, @@ -57,7 +57,7 @@ const isText = (code) => { return true; } if (FIELD_PATH_SYMBOLS.includes(code)) { - // @._[:] + // $._[:] return true; } return false; @@ -155,4 +155,4 @@ const extractTokens = (conditionString) => { .map((token) => token.value); }; -export default extractTokens; \ No newline at end of file +export default extractTokens; diff --git a/src/condition/extract-tokens.test.js b/src/condition/extract-tokens.test.js index b365fcb..0eb9b23 100644 --- a/src/condition/extract-tokens.test.js +++ b/src/condition/extract-tokens.test.js @@ -45,10 +45,10 @@ test.each([ ], ], [ - 'Relative field path', - '@.level1.child1 = "Yes"', + 'Absolute field path', + '$.level1.child1 = "Yes"', [ - '@.level1.child1', '=', '"Yes"', + '$.level1.child1', '=', '"Yes"', ], ], [ diff --git a/src/condition/parse-tokens.js b/src/condition/parse-tokens.js index 4a07d25..1c02b7b 100644 --- a/src/condition/parse-tokens.js +++ b/src/condition/parse-tokens.js @@ -1,6 +1,6 @@ import {SyntaxError} from './errors.js'; -const FIELD_PATH_PATTERN = /^(?:@\.)?[a-zA-Z0-9_]+(?:\[(?:id:[a-zA-Z0-9_]+|[0-9]+)])?(?:\.[a-zA-Z0-9_]+(?:\[(?:id:[a-zA-Z0-9_]+|[0-9]+)])?)*$/; +const FIELD_PATH_PATTERN = /^(?:\$\.)?[a-zA-Z0-9_]+(?:\[(?:id:[a-zA-Z0-9_]+|[0-9]+)])?(?:\.[a-zA-Z0-9_]+(?:\[(?:id:[a-zA-Z0-9_]+|[0-9]+)])?)*$/; const xor = (a, b) => Boolean(a) !== Boolean(b); diff --git a/src/condition/parse-tokens.test.js b/src/condition/parse-tokens.test.js index 84d6980..608b010 100644 --- a/src/condition/parse-tokens.test.js +++ b/src/condition/parse-tokens.test.js @@ -18,16 +18,16 @@ test('should parse simple conjunction condition without grouping', () => { }); }); -test('should parse condition with relative field path', () => { +test('should parse condition with absolute field path', () => { const tokens = [ - '@.level1.child1', '===', '"value1"', + '$.level1.child1', '===', '"value1"', ]; expect(parseTokens(tokens)).toEqual({ condition: [ - {path: '@.level1.child1', operator: 'EQUALS', value: 'value1'}, + {path: '$.level1.child1', operator: 'EQUALS', value: 'value1'}, ], - fieldPaths: ['@.level1.child1'], + fieldPaths: ['$.level1.child1'], }); }); From db11868ac583bb52db611c298c4fefffc7a20422 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Fri, 22 Mar 2024 16:20:34 +0000 Subject: [PATCH 4/6] [Template] Remove debug console log --- src/template/template.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/template/template.js b/src/template/template.js index a548b37..be8f97a 100644 --- a/src/template/template.js +++ b/src/template/template.js @@ -45,7 +45,6 @@ const RecordContext = (extractor, context = new Mustache.Context()) => { path = path.slice(0, -COERCE_BOOL_SUFFIX.length); value = extractValue(path)?.toLowerCase() === 'yes'; } else { - console.log(extractValue); value = extractValue(path); } From 2caa236eabc530810875c2bca423029b9b7d019f Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Fri, 22 Mar 2024 16:24:57 +0000 Subject: [PATCH 5/6] [Path] Add constant for separator --- src/path/constants.js | 1 + src/record/extract-member.js | 6 +++--- src/record/extract-value.js | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/path/constants.js b/src/path/constants.js index 9420ada..770e87a 100644 --- a/src/path/constants.js +++ b/src/path/constants.js @@ -1 +1,2 @@ export const ABSOLUTE_START = '$.'; +export const SEPARATOR = '.'; diff --git a/src/record/extract-member.js b/src/record/extract-member.js index ff91b44..81148f7 100644 --- a/src/record/extract-member.js +++ b/src/record/extract-member.js @@ -1,4 +1,4 @@ -import {isAbsolute, isMetadata} from '../path/index.js'; +import * as Path from '../path/index.js'; /** * Creates an instance of a relative value extractor for members of a complex field. @@ -13,10 +13,10 @@ const extractMember = (extract, complexPath) => (path) => { }; const resolvePath = (parentPath) => (path) => { - if (isMetadata(path) || isAbsolute(path)) { + if (Path.isMetadata(path) || Path.isAbsolute(path)) { return path; } - return parentPath + '.' + path; + return parentPath + Path.SEPARATOR + path; }; const processPath = (resolve) => (path) => { diff --git a/src/record/extract-value.js b/src/record/extract-value.js index e561c5d..00114d6 100644 --- a/src/record/extract-value.js +++ b/src/record/extract-value.js @@ -23,7 +23,10 @@ const valueExtractor = (record) => (path) => { } const caseData = dataExtractor(record); - return caseData ? field(caseData)(path.replace(Path.ABSOLUTE_START, '').split('.').map(parsePathElement)) : undefined; + const parsedPath = path.replace(Path.ABSOLUTE_START, '') + .split(Path.SEPARATOR) + .map(parsePathElement); + return caseData ? field(caseData)(parsedPath) : undefined; }; const metadataExtractor = (record) => (path) => { From 48d450b3228c61eae27508397b41e92bba998278 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Fri, 22 Mar 2024 16:25:49 +0000 Subject: [PATCH 6/6] [Record] Remove duplicate `isMetadata` import --- src/record/extract-value.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/record/extract-value.js b/src/record/extract-value.js index 00114d6..5d464ab 100644 --- a/src/record/extract-value.js +++ b/src/record/extract-value.js @@ -1,4 +1,3 @@ -import {isMetadata} from '../metadata/index.js'; import * as Metadata from '../metadata/index.js'; import * as Path from '../path/index.js'; import comboExtractor from '../path/combo-extractor.js'; @@ -18,7 +17,7 @@ const COLLECTION_ITEM_PATTERN = /^(?[^[\]]+)(?:\[(?:(?\d+)|id:(? const extractValue = (record) => comboExtractor(valueExtractor(record)) const valueExtractor = (record) => (path) => { - if (isMetadata(path)) { + if (Path.isMetadata(path)) { return metadataExtractor(record)(path); }