From bc50ea11ba48a1ff79788d99922279ceb258e372 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Sat, 23 Mar 2024 10:14:29 +0000 Subject: [PATCH 1/6] [Path] Add `root(path)` helper Extract root field identifier from path --- src/path/helpers.js | 7 +++++++ src/path/helpers.test.js | 16 ++++++++++++++++ src/path/index.js | 1 + 3 files changed, 24 insertions(+) create mode 100644 src/path/helpers.js create mode 100644 src/path/helpers.test.js diff --git a/src/path/helpers.js b/src/path/helpers.js new file mode 100644 index 0000000..42c89b5 --- /dev/null +++ b/src/path/helpers.js @@ -0,0 +1,7 @@ +/** + * 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..8ae326d --- /dev/null +++ b/src/path/helpers.test.js @@ -0,0 +1,16 @@ +import {root} from './helpers.js'; + +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'; From 799ec40c959f2629d33b67819740c7a588c800e4 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Sat, 23 Mar 2024 10:15:13 +0000 Subject: [PATCH 2/6] [Path] Add `isRoot(path)` predicate Test whether a path targets a root field --- src/path/predicates.js | 9 +++++++++ src/path/predicates.test.js | 14 ++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/path/predicates.test.js diff --git a/src/path/predicates.js b/src/path/predicates.js index 3581160..11e291c 100644 --- a/src/path/predicates.js +++ b/src/path/predicates.js @@ -3,6 +3,15 @@ import {ABSOLUTE_START} from './constants.js'; export const isAbsolute = (path) => path.slice(0,2) === ABSOLUTE_START; +/** + * 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..9f25b88 --- /dev/null +++ b/src/path/predicates.test.js @@ -0,0 +1,14 @@ +import {isRoot} from './predicates.js'; + +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); + }); +}); From d2d2c7fee005f2b6635b2d00dcec2725334007fe Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Sat, 23 Mar 2024 10:20:48 +0000 Subject: [PATCH 3/6] [Path] Add test and doc for `isAbsolute(path)` --- src/path/predicates.js | 6 ++++++ src/path/predicates.test.js | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/path/predicates.js b/src/path/predicates.js index 11e291c..b648226 100644 --- a/src/path/predicates.js +++ b/src/path/predicates.js @@ -1,6 +1,12 @@ 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; /** diff --git a/src/path/predicates.test.js b/src/path/predicates.test.js index 9f25b88..4baa876 100644 --- a/src/path/predicates.test.js +++ b/src/path/predicates.test.js @@ -1,4 +1,18 @@ -import {isRoot} from './predicates.js'; +import {isAbsolute, 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('isRoot', () => { test.each([ From f39d45884614016e496408b0496c1e5d3070e33a Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Sat, 23 Mar 2024 10:27:07 +0000 Subject: [PATCH 4/6] [Path] Add `absolute(path)` helper Make a path explicitly absolute. --- src/path/helpers.js | 11 +++++++++++ src/path/helpers.test.js | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/path/helpers.js b/src/path/helpers.js index 42c89b5..43e5e0e 100644 --- a/src/path/helpers.js +++ b/src/path/helpers.js @@ -1,3 +1,14 @@ +import {ABSOLUTE_START} from './constants.js'; +import {isAbsolute} 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; + /** * Extract root field from path, excluding absolute path prefix `$.`. * diff --git a/src/path/helpers.test.js b/src/path/helpers.test.js index 8ae326d..a6c17af 100644 --- a/src/path/helpers.test.js +++ b/src/path/helpers.test.js @@ -1,4 +1,17 @@ -import {root} from './helpers.js'; +import {absolute, 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('root', () => { test.each([ From 305dfeb44bfb05bf3983148e4287a1de81e5b547 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Sat, 23 Mar 2024 10:32:46 +0000 Subject: [PATCH 5/6] [Path] Add `isRelative(path)` predicate Test whether a path is relative. --- src/path/predicates.js | 8 ++++++++ src/path/predicates.test.js | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/path/predicates.js b/src/path/predicates.js index b648226..67d749c 100644 --- a/src/path/predicates.js +++ b/src/path/predicates.js @@ -9,6 +9,14 @@ import {ABSOLUTE_START} from './constants.js'; */ 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. diff --git a/src/path/predicates.test.js b/src/path/predicates.test.js index 4baa876..7b01c3e 100644 --- a/src/path/predicates.test.js +++ b/src/path/predicates.test.js @@ -1,4 +1,4 @@ -import {isAbsolute, isRoot} from './predicates.js'; +import {isAbsolute, isRelative, isRoot} from './predicates.js'; describe('isAbsolute', () => { test.each([ @@ -14,6 +14,20 @@ describe('isAbsolute', () => { }); }); +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], From b9f7fb1cbb85c952cb11d9ee64c4bc68772c7e80 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Sat, 23 Mar 2024 10:33:55 +0000 Subject: [PATCH 6/6] [Path] Add `relative(path)` helper Make a path relative when it is not already. --- src/path/helpers.js | 10 +++++++++- src/path/helpers.test.js | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/path/helpers.js b/src/path/helpers.js index 43e5e0e..9dc1e6f 100644 --- a/src/path/helpers.js +++ b/src/path/helpers.js @@ -1,5 +1,5 @@ import {ABSOLUTE_START} from './constants.js'; -import {isAbsolute} from './predicates.js'; +import {isAbsolute, isRelative} from './predicates.js'; /** * Make a path explicitly absolute when it is not already. @@ -9,6 +9,14 @@ import {isAbsolute} from './predicates.js'; */ 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 `$.`. * diff --git a/src/path/helpers.test.js b/src/path/helpers.test.js index a6c17af..a363e5f 100644 --- a/src/path/helpers.test.js +++ b/src/path/helpers.test.js @@ -1,4 +1,4 @@ -import {absolute, root} from './helpers.js'; +import {absolute, relative, root} from './helpers.js'; describe('absolute', () => { test.each([ @@ -13,6 +13,19 @@ describe('absolute', () => { }); }); +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],