diff --git a/src/path/helpers.js b/src/path/helpers.js new file mode 100644 index 0000000..9dc1e6f --- /dev/null +++ b/src/path/helpers.js @@ -0,0 +1,26 @@ +import {ABSOLUTE_START} from './constants.js'; +import {isAbsolute, isRelative} from './predicates.js'; + +/** + * Make a path explicitly absolute when it is not already. + * + * @param {string} path - Path to make explicitly absolute + * @returns {string} Absolute path (i.e. starts with `$.`) + */ +export const absolute = (path) => isAbsolute(path) ? path : ABSOLUTE_START + path; + +/** + * Make a path relative when it is not already. + * + * @param {string} path - Path to make relative + * @returns {string} Relative path (i.e. does not start with `$.`) + */ +export const relative = (path) => isRelative(path) ? path : path.slice(2); + +/** + * Extract root field from path, excluding absolute path prefix `$.`. + * + * @param {string} path - Field path from which to extract root field + * @returns {string} Root field identifier + */ +export const root = (path) => path.match(/^(?:\$\.)?([a-zA-Z0-9_]+)/)?.[1]; diff --git a/src/path/helpers.test.js b/src/path/helpers.test.js new file mode 100644 index 0000000..a363e5f --- /dev/null +++ b/src/path/helpers.test.js @@ -0,0 +1,42 @@ +import {absolute, relative, root} from './helpers.js'; + +describe('absolute', () => { + test.each([ + ['field1', '$.field1'], + ['$.field1', '$.field1'], + ['$.level1.level2', '$.level1.level2'], + ['level1.level2', '$.level1.level2'], + ['collection[0]', '$.collection[0]'], + ['$.collection[0]', '$.collection[0]'], + ])(`should path '%s' be root: %s`, (path, expectedAbsolute) => { + expect(absolute(path)).toBe(expectedAbsolute); + }); +}); + +describe('relative', () => { + test.each([ + ['field1', 'field1'], + ['$.field1', 'field1'], + ['$.level1.level2', 'level1.level2'], + ['level1.level2', 'level1.level2'], + ['collection[0]', 'collection[0]'], + ['$.collection[0]', 'collection[0]'], + ])(`should path '%s' be root: %s`, (path, expectedRelative) => { + expect(relative(path)).toBe(expectedRelative); + }); +}); + +describe('root', () => { + test.each([ + ['$', undefined], + ['$.', undefined], + ['field1', 'field1'], + ['$.field1', 'field1'], + ['$.level1.level2', 'level1'], + ['level1.level2', 'level1'], + ['collection[0]', 'collection'], + ['$.collection[0]', 'collection'], + ])(`should path '%s' be root: %s`, (path, expectedRoot) => { + expect(root(path)).toBe(expectedRoot); + }); +}); diff --git a/src/path/index.js b/src/path/index.js index 3b98b2a..375e77a 100644 --- a/src/path/index.js +++ b/src/path/index.js @@ -1,2 +1,3 @@ export * from './constants.js'; +export * from './helpers.js'; export * from './predicates.js'; diff --git a/src/path/predicates.js b/src/path/predicates.js index 3581160..67d749c 100644 --- a/src/path/predicates.js +++ b/src/path/predicates.js @@ -1,8 +1,31 @@ import isMetadata from '../metadata/is-metadata.js'; import {ABSOLUTE_START} from './constants.js'; +/** + * Test whether a path is explicitly absolute. + * + * @param {string} path - Path to be tested + * @returns {boolean} Whether the path is explicitly absolute (i.e. starts with `$.`) + */ export const isAbsolute = (path) => path.slice(0,2) === ABSOLUTE_START; +/** + * Test whether a path is relative. + * + * @param {string} path - Path to be tested + * @returns {boolean} Whether the path is relative (i.e. does not start with `$.`) + */ +export const isRelative = (path) => !isAbsolute(path); + +/** + * Test whether a path points to a root field. + * Note: This will return true for relative paths targeting top-level members for relative to current location. + * + * @param {string} path - Path to be tested + * @returns {boolean} Whether the path points to a root field + */ +export const isRoot = (path) => !!path.match(/^(?:\$\.)?[a-zA-Z0-9_]+$/); + export { // Reexport `isMetadata()` for convenience isMetadata, diff --git a/src/path/predicates.test.js b/src/path/predicates.test.js new file mode 100644 index 0000000..7b01c3e --- /dev/null +++ b/src/path/predicates.test.js @@ -0,0 +1,42 @@ +import {isAbsolute, isRelative, isRoot} from './predicates.js'; + +describe('isAbsolute', () => { + test.each([ + ['field1', false], + ['$.field1', true], + ['$.level1.level2', true], + ['level1.level2', false], + ['collection[0]', false], + ['$.collection[0]', true], + ['collection[0]', false], + ])(`should path '%s' be root: %s`, (path, expectedAbsolute) => { + expect(isAbsolute(path)).toBe(expectedAbsolute); + }); +}); + +describe('isRelative', () => { + test.each([ + ['field1', true], + ['$.field1', false], + ['$.level1.level2', false], + ['level1.level2', true], + ['collection[0]', true], + ['$.collection[0]', false], + ['collection[0]', true], + ])(`should path '%s' be root: %s`, (path, expectedRelative) => { + expect(isRelative(path)).toBe(expectedRelative); + }); +}); + +describe('isRoot', () => { + test.each([ + ['field1', true], + ['$.field1', true], + ['$.level1.level2', false], + ['level1.level2', false], + ['$.collection[0]', false], + ['collection[0]', false], + ])(`should path '%s' be root: %s`, (path, expectedRoot) => { + expect(isRoot(path)).toBe(expectedRoot); + }); +});