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'], }); }); diff --git a/src/path/constants.js b/src/path/constants.js new file mode 100644 index 0000000..770e87a --- /dev/null +++ b/src/path/constants.js @@ -0,0 +1,2 @@ +export const ABSOLUTE_START = '$.'; +export const SEPARATOR = '.'; 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, +}; 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..81148f7 100644 --- a/src/record/extract-member.js +++ b/src/record/extract-member.js @@ -1,3 +1,5 @@ +import * as Path from '../path/index.js'; + /** * Creates an instance of a relative value extractor for members of a complex field. * @@ -11,10 +13,10 @@ const extractMember = (extract, complexPath) => (path) => { }; const resolvePath = (parentPath) => (path) => { - if (path[0] === '[') { + 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-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..5d464ab 100644 --- a/src/record/extract-value.js +++ b/src/record/extract-value.js @@ -1,5 +1,5 @@ -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:(?[^[\]]+))\])?$/; @@ -17,12 +17,15 @@ 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); } const caseData = dataExtractor(record); - return caseData ? field(caseData)(path.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) => { 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: { 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); }