diff --git a/src/path/helpers.js b/src/path/helpers.js index 9dc1e6f..014079d 100644 --- a/src/path/helpers.js +++ b/src/path/helpers.js @@ -1,4 +1,4 @@ -import {ABSOLUTE_START} from './constants.js'; +import {ABSOLUTE_START, SEPARATOR} from './constants.js'; import {isAbsolute, isRelative} from './predicates.js'; /** @@ -9,6 +9,25 @@ import {isAbsolute, isRelative} from './predicates.js'; */ export const absolute = (path) => isAbsolute(path) ? path : ABSOLUTE_START + path; +/** + * Build a field path out of its individual parts. + * + * @param {...string} parts - Parts to assemble to build full path + * @returns {string} Assembled field path + */ +export const build = (...parts) => parts.join(SEPARATOR); + +/** + * Build a path to a given item inside a collection. + * + * @param {string} collectionPath - Path to the collection + * @param {number | string} item - Index or ID of the item + * @returns {string} Path to the item + */ +export const buildCollectionItem = (collectionPath, item) => { + return build(`${collectionPath}[${Number.isInteger(item) ? item : 'id:' + item}]`, 'value'); +}; + /** * Make a path relative when it is not already. * diff --git a/src/path/helpers.test.js b/src/path/helpers.test.js index a363e5f..3b55d6d 100644 --- a/src/path/helpers.test.js +++ b/src/path/helpers.test.js @@ -1,4 +1,4 @@ -import {absolute, relative, root} from './helpers.js'; +import {absolute, build, buildCollectionItem, relative, root} from './helpers.js'; describe('absolute', () => { test.each([ @@ -13,6 +13,36 @@ describe('absolute', () => { }); }); +describe('build', () => { + test.each([ + {parts: ['field1'], expected: 'field1'}, + {parts: ['$.field1'], expected: '$.field1'}, + {parts: ['level1', 'level2'], expected: 'level1.level2'}, + {parts: ['$.level1', 'level2'], expected: '$.level1.level2'}, + {parts: ['$.level1', 'level2', 'level3'], expected: '$.level1.level2.level3'}, + {parts: ['$.level1.level2', 'level3'], expected: '$.level1.level2.level3'}, + {parts: ['collection[0]', 'value', 'member'], expected: 'collection[0].value.member'}, + {parts: ['$.collection[0]', 'value', 'member'], expected: '$.collection[0].value.member'}, + ])('should build path: $expected', ({parts, expected}) => { + expect(build(...parts)).toBe(expected); + }); +}); + +describe('buildCollectionItem', () => { + test.each([ + {collection: 'collection', item: 0, expected: 'collection[0].value'}, + {collection: '$.collection', item: 0, expected: '$.collection[0].value'}, + {collection: 'collection', item: '0', expected: 'collection[id:0].value'}, + {collection: '$.collection', item: '0', expected: '$.collection[id:0].value'}, + {collection: '$.complex.collection', item: '0', expected: '$.complex.collection[id:0].value'}, + {collection: 'collection', item: 'some-id', expected: 'collection[id:some-id].value'}, + {collection: '$.collection', item: 'some-id', expected: '$.collection[id:some-id].value'}, + {collection: '$.complex.collection', item: 'some-id', expected: '$.complex.collection[id:some-id].value'}, + ])('should build path: $expected', ({collection, item, expected}) => { + expect(buildCollectionItem(collection, item)).toBe(expected); + }); +}); + describe('relative', () => { test.each([ ['field1', 'field1'], diff --git a/src/record/extract-collection-item.js b/src/record/extract-collection-item.js index eb279f3..7addf72 100644 --- a/src/record/extract-collection-item.js +++ b/src/record/extract-collection-item.js @@ -1,4 +1,4 @@ -import {isAbsolute, isMetadata} from '../path/index.js'; +import {buildCollectionItem, isAbsolute, isMetadata} from '../path/index.js'; /** * Creates an instance of a relative value extractor for items of a collection field. @@ -15,14 +15,14 @@ const extractCollectionItem = (extract, collectionPath) => (path) => { const resolvePath = (parentPath) => (path) => { if (Number.isInteger(path)) { - return `${parentPath}[${path}].value`; + return buildCollectionItem(parentPath, path); } if (isMetadata(path) || isAbsolute(path)) { return path; } - return `${parentPath}[id:${path}].value`; + return buildCollectionItem(parentPath, path); }; const processPath = (resolve) => (path) => { diff --git a/src/record/extract-collection-member.js b/src/record/extract-collection-member.js index b1020d6..5fd8650 100644 --- a/src/record/extract-collection-member.js +++ b/src/record/extract-collection-member.js @@ -1,4 +1,4 @@ -import {isAbsolute, isMetadata} from '../path/index.js'; +import {build, buildCollectionItem, isAbsolute, isMetadata} from '../path/index.js'; /** * Creates an instance of a relative value extractor for members of a complex collection field. @@ -17,11 +17,7 @@ const resolvePath = (parentPath) => (item) => (path) => { return path; } - if (Number.isInteger(item)) { - return `${parentPath}[${item}].value.${path}`; - } - - return `${parentPath}[id:${item}].value.${path}`; + return build(buildCollectionItem(parentPath, item), path); }; const processPath = (resolve) => (path) => { diff --git a/src/record/extract-member.js b/src/record/extract-member.js index 81148f7..bc2d028 100644 --- a/src/record/extract-member.js +++ b/src/record/extract-member.js @@ -16,7 +16,7 @@ const resolvePath = (parentPath) => (path) => { if (Path.isMetadata(path) || Path.isAbsolute(path)) { return path; } - return parentPath + Path.SEPARATOR + path; + return Path.build(parentPath, path); }; const processPath = (resolve) => (path) => {