From e6539618583b1f34e131435f16e1817328fb1297 Mon Sep 17 00:00:00 2001 From: Sofien-Sellami <73444179+Sofien-Sellami@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:56:40 +0100 Subject: [PATCH] Add support for array input for all constraints in HangingProtocolService validator (#3322) Co-authored-by: Bill Wallace --- .../HangingProtocolService/lib/validator.js | 458 ++++++++++-- .../lib/validator.test.js | 675 ++++++++++++++---- .../platform/extensions/modules/hpModule.md | 18 +- 3 files changed, 977 insertions(+), 174 deletions(-) diff --git a/platform/core/src/services/HangingProtocolService/lib/validator.js b/platform/core/src/services/HangingProtocolService/lib/validator.js index 498ae3201..3be5b16de 100644 --- a/platform/core/src/services/HangingProtocolService/lib/validator.js +++ b/platform/core/src/services/HangingProtocolService/lib/validator.js @@ -1,69 +1,240 @@ import validate from 'validate.js'; +/** + * check if the value is strictly equal to options + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = 'abc' (Fail) + * = ['abc'] (Fail) + * = ['abc', 'def', 'GHI'] (Valid) + * = ['abc', 'GHI', 'def'] (Fail) + * = ['abc', 'def'] (Fail) + * + * value = 'Attenuation Corrected' + * testValue = 'Attenuation Corrected' (Valid) + * testValue = 'Attenuation' (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = ['Attenuation Corrected'] (Valid) + * = 'Attenuation Corrected' (Valid) + * = 'Attenuation' (Fail) + * + * */ +validate.validators.equals = function(value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); -validate.validators.equals = function(value, options, key, attributes) { - const testValue = options?.value ?? options; - if (value !== testValue) { - return key + 'must equal ' + testValue; + // If options is an array, then we need to validate each element in the array + if (Array.isArray(testValue)) { + // If the array has only one element, then we need to compare the value to that element + if (testValue.length !== dicomArrayValue.length) { + return `${key} must be an array of length ${testValue.length}`; + } else { + for (let i = 0; i < testValue.length; i++) { + if (testValue[i] !== dicomArrayValue[i]) { + return `${key} ${testValue[i]} must equal ${dicomArrayValue[i]}`; + } + } + } + } else if (testValue !== dicomArrayValue[0]) { + return `${key} must equal ${testValue}`; } }; - +/** + * check if the value is not equal to options + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = 'abc' (Valid) + * = ['abc'] (Valid) + * = ['abc', 'def', 'GHI'] (Fail) + * = ['abc', 'GHI', 'def'] (Valid) + * = ['abc', 'def'] (Valid) + * + * value = 'Attenuation Corrected' + * = 'Attenuation Corrected' (Fail) + * = 'Attenuation' (Valid) + * + * value = ['Attenuation Corrected'] + * testValue = ['Attenuation Corrected'] (Fail) + * = 'Attenuation Corrected' (Fail) + * = 'Attenuation' (Fail) + * */ validate.validators.doesNotEqual = function(value, options, key) { - const testValue = options?.value ?? options; - if (value === testValue) { - return key + 'cannot equal ' + testValue; + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + + if (Array.isArray(testValue)) { + if (testValue.length === dicomArrayValue.length) { + let score = 0; + testValue.forEach((x, i) => { + if (x === dicomArrayValue[i]) { + score++; + } + }); + if (score === testValue.length) { + return `${key} must not equal to ${testValue}`; + } + } + } else if (testValue === dicomArrayValue[0]) { + console.debug(dicomArrayValue, testValue); + return `${key} must not equal to ${testValue}`; } }; +/** + * Check if a value includes one or more specified options. + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = ‘abc’ (Fail) + * = ‘dog’ (Fail) + * = [‘abc’] (Valid) + * = [‘att’, ‘abc’] (Valid) + * = ['abc', 'def', 'dog'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = 'Attenuation Corrected' (Fail) + * = ['Attenuation Corrected', 'Corrected'] (Valid) + * = ['Attenuation', 'Corrected'] (Fail) + * + * value = 'Attenuation Corrected' + * testValue = ['Attenuation Corrected', 'Corrected'] (Valid) + * = ['Attenuation', 'Corrected'] (Fail) + * */ +validate.validators.includes = function(value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + + if (Array.isArray(testValue)) { + const includedValues = testValue.filter(el => dicomArrayValue.includes(el)); + if (includedValues.length === 0) { + return `${key} must include at least one of the following values: ${testValue.join( + ', ' + )}`; + } + } else return `${key} ${testValue} must be an array`; + // else if (!value.includes(testValue)) { + // return `${key} ${value} must include ${testValue}`; + // } +}; +/** + * Check if a value does not include one or more specified options. + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = ['Corr'] (Valid) + * = 'abc' (Fail) + * = ['abc'] (Fail) + * = [‘att’, ‘cor’] (Valid) + * = ['abc', 'def', 'dog'] (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = 'Attenuation Corrected' (Fail) + * = ['Attenuation Corrected', 'Corrected'] (Fail) + * = ['Attenuation', 'Corrected'] (Valid) + * + * value = 'Attenuation Corrected' + * testValue = ['Attenuation Corrected', 'Corrected'] (Fail) + * = ['Attenuation', 'Corrected'] (Valid) + * */ +validate.validators.doesNotInclude = function(value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + + // if (!Array.isArray(value) || value.length === 1) { + // return `${key} is not allowed as a single value`; + // } + if (Array.isArray(testValue)) { + const includedValues = testValue.filter(el => dicomArrayValue.includes(el)); + if (includedValues.length > 0) { + return `${key} must not include the following value: ${includedValues}`; + } + } else return `${key} ${testValue} must be an array`; +}; // Ignore case contains. // options testValue MUST be in lower case already, otherwise it won't match -validate.validators.containsI = function (value, options, key) { - const testValue = options?.value ?? options; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Valid) + * = ‘corr’ (Valid) + * = [‘att’, ‘cor’] (Valid) + * = [‘Att’, ‘Wall’] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Valid) + * = 'dog' (Fail) + * = ['gh', 'de'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * */ +validate.validators.containsI = function(value, options, key) { + const testValue = getTestValue(options); if (Array.isArray(value)) { if ( - value.some( - item => !validate.validators.containsI(item.toLowerCase(), options, key) - ) + value.some( + item => !validate.validators.containsI(item.toLowerCase(), options, key) + ) ) { return undefined; } return `No item of ${value.join(',')} contains ${JSON.stringify( - testValue + testValue )}`; } if (Array.isArray(testValue)) { if ( - testValue.some( - subTest => !validate.validators.containsI(value, subTest, key) - ) + testValue.some( + subTest => + !validate.validators.containsI(value, subTest.toLowerCase(), key) + ) ) { return; } return `${key} must contain at least one of ${testValue.join(',')}`; } if ( - testValue && - value.indexOf && - value.toLowerCase().indexOf(testValue) === -1 + testValue && + value.indexOf && + value.toLowerCase().indexOf(testValue.toLowerCase()) === -1 ) { return key + 'must contain any case of' + testValue; } }; - +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Valid) + * = ‘corr’ (Fail) + * = [‘att’, ‘cor’] (Fail) + * = [‘Att’, ‘Wall’] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Valid) + * = 'dog' (Fail) + * = ['cat', 'de'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * */ validate.validators.contains = function(value, options, key) { - const testValue = options?.value ?? options; + const testValue = getTestValue(options); if (Array.isArray(value)) { if (value.some(item => !validate.validators.contains(item, options, key))) { return undefined; } return `No item of ${value.join(',')} contains ${JSON.stringify( - testValue + testValue )}`; } if (Array.isArray(testValue)) { if ( - testValue.some( - subTest => !validate.validators.contains(value, subTest, key) - ) + testValue.some( + subTest => !validate.validators.contains(value, subTest, key) + ) ) { return; } @@ -73,50 +244,235 @@ validate.validators.contains = function(value, options, key) { return key + 'must contain ' + testValue; } }; - +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘corr’ (Valid) + * = [‘att’, ‘cor’] (Valid) + * = [‘Att’, ‘Wall’] (Fail) + * = [‘cat’, ‘dog’] (Valid) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Fail) + * = 'dog' (Valid) + * = ['cat', 'de'] (Fail) + * = ['cat', 'dog'] (Valid) + * + * */ validate.validators.doesNotContain = function(value, options, key) { - if (options && value.indexOf && value.indexOf(options.value) !== -1) { - return key + 'cannot contain ' + options.value; + const containsResult = validate.validators.contains(value, options, key); + if (!containsResult) { + return `No item of ${value} should contain ${getTestValue(options)}`; } }; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘corr’ (Fail) + * = [‘att’, ‘cor’] (Fail) + * = [‘Att’, ‘Wall’] (Fail) + * = [‘cat’, ‘dog’] (Valid) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'DEF' (Fail) + * = 'dog' (Valid) + * = ['cat', 'gh'] (Fail) + * = ['cat', 'dog'] (Valid) + * + * */ +validate.validators.doesNotContainI = function(value, options, key) { + const containsResult = validate.validators.containsI(value, options, key); + if (!containsResult) { + return `No item of ${value} should not contain ${getTestValue(options)}`; + } +}; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘Att’ (Fail) + * = ['cat', 'dog', 'Att'] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'deg' (Valid) + * = ['cat', 'GH'] (Valid) + * = ['cat', 'gh'] (Fail) + * = ['cat', 'dog'] (Fail) + * + * */ validate.validators.startsWith = function(value, options, key) { - if (options && value.startsWith && !value.startsWith(options.value)) { - return key + 'must start with ' + options.value; + let testValues = getTestValue(options); + + if (typeof testValues === 'string') { + testValues = [testValues]; + } + + if (typeof value === 'string') { + if (!testValues.some(testValue => value.startsWith(testValue))) { + return key + ' must start with any of these values: ' + testValues; + } + } else if (Array.isArray(value)) { + let valid = false; + for (let i = 0; i < value.length; i++) { + for (let j = 0; j < testValues.length; j++) { + if (value[i].startsWith(testValues[j])) { + valid = true; // set valid flag to true if a match is found + break; + } + } + if (valid) { + return undefined; // break out of loop if a match is found + } + } + + if (!valid) { + return key + ' must start with any of these values: ' + testValues; // return undefined if no match is found + } + } else { + return 'Value must be a string or an array'; } }; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘TED’ (Fail) + * = ‘ted’ (Valid) + * = ['cat', 'dog', 'ted'] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'deg' (Valid) + * = ['cat', 'HI'] (Valid) + * = ['cat', 'hi'] (Fail) + * = ['cat', 'dog'] (Fail) + * + * */ validate.validators.endsWith = function(value, options, key) { - if (options && value.endsWith && !value.endsWith(options.value)) { - return key + 'must end with ' + options.value; + let testValues = getTestValue(options); + + if (typeof testValues === 'string') { + testValues = [testValues]; + } + + if (typeof value === 'string') { + if (!testValues.some(testValue => value.endsWith(testValue))) { + return key + ' must end with any of these values: ' + testValues; + } + } else if (Array.isArray(value)) { + let valid = false; + for (let i = 0; i < value.length; i++) { + for (let j = 0; j < testValues.length; j++) { + if (value[i].endsWith(testValues[j])) { + valid = true; // set valid flag to true if a match is found + break; + } + } + if (valid) { + return undefined; // break out of loop if a match is found + } + } + + if (!valid) { + return key + ' must end with any of these values: ' + testValues; // return undefined if no match is found + } + } else { + return key + ' must be a string or an array'; } }; - -const getTestValue = options => options?.value ?? options; - +/** + * @example + * value = 30 + * testValue = 20 (Valid) + * = 40 (Fail) + * + * */ validate.validators.greaterThan = function(value, options, key) { const testValue = getTestValue(options); - if (value === undefined || value === null || value <= testValue) { - return key + 'with value ' + value + ' must be greater than ' + testValue; + if (Array.isArray(value) || typeof value === 'string') { + return `${key} is not allowed as an array or string`; + } + if (Array.isArray(testValue)) { + if (testValue.length === 1) { + if (!(value >= testValue[0])) { + return `${key} must be greater than or equal to ${testValue[0]}, but was ${value}`; + } + } else if (testValue.length > 1) { + return key + ' must be an array of length 1'; + } + } else { + if (!(value >= testValue)) { + return key + ' must be greater than ' + testValue; + } } }; +/** + * @example + * value = 30 + * testValue = 40 (Valid) + * = 20 (Fail) + * + * */ +validate.validators.lessThan = function(value, options, key) { + const testValue = getTestValue(options); + if (Array.isArray(testValue)) { + if (testValue.length === 1) { + if (!(value <= testValue[0])) { + return `${key} must be less than or equal to ${testValue[0]}, but was ${value}`; + } + } else if (testValue.length > 1) { + return key + ' must be an array of length 1'; + } + } else { + if (!(value <= testValue)) { + return key + ' must be less than ' + testValue; + } + } +}; +/** + * @example + * + * value = 50 + * testValue = [10,60] (Valid) + * = [60, 10] (Valid) + * = [0, 10] (Fail) + * = [70, 80] (Fail) + * = 45 (Fail) + * = [45] (Fail) + * + * */ validate.validators.range = function(value, options, key) { const testValue = getTestValue(options); - if (value === undefined || value < testValue[0] || value > testValue[1]) { - return ( - key + - 'with value ' + - value + - ' must be between ' + - testValue[0] + - ' and ' + - testValue[1] - ); - } + if (Array.isArray(testValue) && testValue.length === 2) { + const min = Math.min(testValue[0], testValue[1]); + const max = Math.max(testValue[0], testValue[1]); + if (value === undefined || value < min || value > max) { + return `${key} with value ${value} must be between ${min} and ${max}`; + } + } else return `${key} must be an array of length 2`; }; validate.validators.notNull = value => - value === null || value === undefined ? 'Value is null' : undefined; - + value === null || value === undefined ? 'Value is null' : undefined; +const getTestValue = options => { + if (Array.isArray(options)) { + return options.map(option => option?.value ?? option); + } else { + return options?.value ?? options; + } +}; +const dicomTagToArray = value => { + let dicomArrayValue; + if (!Array.isArray(value)) { + dicomArrayValue = [value]; + } else { + dicomArrayValue = [...value]; + } + return dicomArrayValue; +}; export default validate; diff --git a/platform/core/src/services/HangingProtocolService/lib/validator.test.js b/platform/core/src/services/HangingProtocolService/lib/validator.test.js index 67b7ac52e..da6f46f04 100644 --- a/platform/core/src/services/HangingProtocolService/lib/validator.test.js +++ b/platform/core/src/services/HangingProtocolService/lib/validator.test.js @@ -1,139 +1,570 @@ import validate from './validator.js'; describe('validator', () => { - const attributeMap = { - str: 'string', - upper: 'UPPER', - num: 3, - nullValue: null, - list: ['abc', 'def', 'GHI'], - }; + const attributeMap = { + str: 'Attenuation Corrected', + upper: 'UPPER', + num: 3, + nullValue: null, + list: ['abc', 'def', 'GHI'], + listStr: ['Attenuation Corrected'], + }; - const options = { - format: 'grouped', - }; + const options = { + format: 'grouped', + }; - describe('contains', () => { - it('returns match any list contains', () => { - expect( - validate(attributeMap, { list: { contains: 'a' } }, [options]) - ).toBeUndefined(); - expect( - validate(attributeMap, { str: { contains: 'i' } }, [options]) - ).toBeUndefined(); - expect( - validate(attributeMap, { str: { contains: ['i'] } }, [options]) - ).toBeUndefined(); - expect( - validate(attributeMap, { list: { contains: ['a'] } }, [options]) - ).toBeUndefined(); - expect( - validate(attributeMap, { list: { contains: ['z', 'd'] } }, [options]) - ).toBeUndefined(); - expect( - validate(attributeMap, { list: { contains: ['z'] } }, [options]) - ).not.toBeUndefined(); + describe('equals', () => { + it('returned undefined on strictly equals', () => { + expect( + validate(attributeMap, { listStr: { equals: ['Attenuation'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { listStr: { equals: 'Attenuation' } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { listStr: { equals: 'Attenuation Corrected' } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { listStr: { equals: ['Attenuation Corrected'] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { equals: 'Attenuation Corrected' } }, [ + options, + ]) + ).toBeUndefined(); + + expect( + validate( + attributeMap, + { str: { equals: { value: 'Attenuation Corrected' } } }, + [options] + ) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { equals: ['Attenuation Corrected'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { equals: ['Attenuation'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { equals: ['abc', 'def', 'GHI'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { equals: ['abc', 'GHI', 'def'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { list: { equals: { value: ['abc', 'def', 'GHI'] } } }, + [options] + ) + ).toBeUndefined(); + }); }); - }); + describe('doesNotEqual', () => { + it('returns undefined if value does not equal ', () => { + expect( + validate( + attributeMap, + { listStr: { doesNotEqual: 'Attenuation Corrected' } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { listStr: { doesNotEqual: ['Attenuation Corrected'] } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { listStr: { doesNotEqual: 'Attenuation' } }, [ + options, + ]) + ).toBeUndefined(); - describe('containsI', () => { - it('returns match any list contains case insensitive', () => { - expect( - validate(attributeMap, { upper: { containsI: ['bye', 'pre'] } }, [ - options, - ]) - ).not.toBeUndefined(); - expect( - validate(attributeMap, { list: { containsI: 'hi' } }, [options]) - ).toBeUndefined(); - expect( - validate(attributeMap, { list: { containsI: ['hi', 'bye'] } }, [ - options, - ]) - ).toBeUndefined(); - expect( - validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [ - options, - ]) - ).toBeUndefined(); - expect( - validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options]) - ).toBeUndefined(); - expect( - validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [ - options, - ]) - ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotEqual: 'Attenuation' } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { str: { doesNotEqual: { value: 'Attenuation' } } }, + [options] + ) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotEqual: ['Attenuation'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotEqual: ['abc', 'def'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { list: { doesNotEqual: ['abc', 'GHI', 'def'] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { list: { doesNotEqual: ['abc', 'def', 'GHI'] } }, + [options] + ) + ).not.toBeUndefined(); + }); }); - }); + describe('includes', () => { + it('returns match any list includes', () => { + expect( + validate( + attributeMap, + { listStr: { includes: 'Attenuation Corrected' } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { listStr: { includes: ['Attenuation Corrected'] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { listStr: { includes: ['Attenuation Corrected', 'Corrected'] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { listStr: { includes: ['Attenuation', 'Corrected'] } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { str: { includes: ['Attenuation Corrected', 'Corrected'] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { str: { includes: ['Attenuation', 'Corrected'] } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { includes: ['abc'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { includes: ['GHI', 'HI'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { includes: ['HI', 'bye'] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('doesNotInclude', () => { + it('returns undefined if list does not includes', () => { + expect( + validate( + attributeMap, + { listStr: { doesNotInclude: 'Attenuation Corrected' } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { + listStr: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] }, + }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { listStr: { doesNotInclude: ['Attenuation', 'Corrected'] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { str: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { str: { doesNotInclude: ['Attenuation', 'Corrected'] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: ['Corr'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: 'abc' } }, [options]) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { list: { doesNotInclude: { value: ['abc'] } } }, + [options] + ) + ).not.toBeUndefined(); + expect( + validate( + attributeMap, + { list: { doesNotInclude: { value: ['att', 'cor'] } } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { list: { doesNotInclude: { value: ['abc', 'def', 'dog'] } } }, + [options] + ) + ).not.toBeUndefined(); + }); + }); + describe('containsI', () => { + it('returns match any list contains case insensitive', () => { + expect( + validate(attributeMap, { upper: { containsI: ['hi', 'pre'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: 'hi' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['ghi', 'bye'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [ + options, + ]) + ).toBeUndefined(); + }); + }); + describe('contains', () => { + it('returns match any list contains', () => { + expect( + validate(attributeMap, { str: { contains: 'Corr' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: { value: 'Corr' } } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: ['Corr'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: ['corr'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: ['Att', 'Wall'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { contains: 'GH' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { contains: ['ab'] } }, [options]) + ).toBeUndefined(); - describe('equals', () => { - it('returned undefined on equals', () => { - expect( - validate(attributeMap, { str: { equals: attributeMap.str } }, [options]) - ).toBeUndefined(); - expect( - validate( - attributeMap, - { num: { equals: { value: attributeMap.num } } }, - [options] - ) - ).toBeUndefined(); + expect( + validate(attributeMap, { list: { contains: ['z', 'bc'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { contains: ['z'] } }, [options]) + ).not.toBeUndefined(); + }); }); - it('returns error on not equals', () => { - expect( - validate(attributeMap, { str: { equals: 'abc' } }, [options]) - ).not.toBeUndefined(); - expect( - validate( - attributeMap, - { num: { equals: { value: 1 + attributeMap.num } } }, - [options] - ) - ).not.toBeUndefined(); + describe('doesNotContain', () => { + it('returns undefined if string does not contain specified value', () => { + expect( + validate(attributeMap, { str: { doesNotContain: ['att', 'wall'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: 'Corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: 'corr' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: { value: 'corr' } } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['att', 'cor'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['Att', 'cor'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['bye', 'hi'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContain: ['GHI', 'hi'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContain: ['hi'] } }, [options]) + ).toBeUndefined(); + }); }); - }); - - describe('greaterThan', () => { - it('returns undefined on greaterThan', () => { - expect( - validate( - attributeMap, - { num: { greaterThan: { value: attributeMap.num - 1 } } }, - [options] - ) - ).toBeUndefined(); - expect( - validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [ - options, - ]) - ).toBeUndefined(); + describe('doesNotContainI', () => { + it('returns undefined if string does not contain specified value', () => { + expect( + validate(attributeMap, { str: { doesNotContainI: 'corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: 'Corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['att', 'cor'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['Att', 'wall'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['bye', 'hi'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: 'bye' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [ + options, + ]) + ).not.toBeUndefined(); + }); + }); + describe('startsWith', () => { + it('returns undefined if string starts with specified value', () => { + expect( + validate(attributeMap, { str: { startsWith: { value: 'Atte' } } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { startsWith: 'Att' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { startsWith: ['cat', 'dog', 'Att'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { startsWith: ['cat', 'dog'] } }, [ + options, + ]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { startsWith: ['GH'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { startsWith: ['de', 'bye'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { startsWith: ['hi', 'bye'] } }, [ + options, + ]) + ).not.toBeUndefined(); + }); + }); + describe('endsWith', () => { + it('returns undefined if string ends with specified value', () => { + expect( + validate(attributeMap, { str: { endsWith: 'ted' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: { value: 'ted' } } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: ['ted'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: ['Att'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: ['cat', 'dog', 'ted'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { endsWith: ['HI'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { endsWith: ['bc', 'dog', 'ted'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { endsWith: ['bye', 'dog'] } }, [ + options, + ]) + ).not.toBeUndefined(); + }); }); - it('returns error on not greater than', () => { - expect( - validate( - attributeMap, - { num: { greaterThan: { value: attributeMap.num } } }, - [options] - ) - ).not.toBeUndefined(); - expect( - validate(attributeMap, { num: { greaterThan: attributeMap.num } }, [ - options, - ]) - ).not.toBeUndefined(); + describe('greaterThan', () => { + it('returns undefined on greaterThan', () => { + expect( + validate( + attributeMap, + { num: { greaterThan: { value: attributeMap.num - 1 } } }, + [options] + ) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { num: { greaterThan: [attributeMap.num - 1] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate( + attributeMap, + { num: { greaterThan: [attributeMap.num + 1] } }, + [options] + ) + ).not.toBeUndefined(); + }); }); - - it('returns error on undefined value', () => { - expect( - validate( - attributeMap, - { numUndefined: { greaterThan: { value: 3 } } }, - [options] - ) - ).not.toBeUndefined(); + describe('lessThan', () => { + it('returns undefined on lessThan', () => { + expect( + validate( + attributeMap, + { num: { lessThan: { value: attributeMap.num + 1 } } }, + [options] + ) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: attributeMap.num + 1 } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: [attributeMap.num + 1] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: [attributeMap.num - 1] } }, [ + options, + ]) + ).not.toBeUndefined(); + }); + }); + describe('range', () => { + it('returns undefined if the value is between', () => { + expect( + validate( + attributeMap, + { num: { range: [attributeMap.num + 1, attributeMap.num - 1] } }, + [options] + ) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { range: [1, 4] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { range: [1, 2] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { num: { range: [4, 5] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { num: { range: [5] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { num: { range: 5 } }, [options]) + ).not.toBeUndefined(); + }); }); - }); }); diff --git a/platform/docs/docs/platform/extensions/modules/hpModule.md b/platform/docs/docs/platform/extensions/modules/hpModule.md index 67bbd7ec8..0c78b1db3 100644 --- a/platform/docs/docs/platform/extensions/modules/hpModule.md +++ b/platform/docs/docs/platform/extensions/modules/hpModule.md @@ -246,7 +246,23 @@ A list of criteria for the protocol along with the provided points for ranking. - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] - + + - | Rule | Single Value | Array Value | Example | + |--- |--- |--- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | equals | === | All members are === in same order | value = ['abc', 'def', 'GHI']
testValue = 'abc' (Fail)

= ['abc'] (Fail)

= ['abc', 'def', 'GHI'] (Valid)

= ['abc', 'GHI', 'def'] (Fail)

= ['abc', 'def'] (Fail)

value = 'Attenuation Corrected'
testValue = 'Attenuation Corrected' (Valid)
= 'Attenuation' (Fail)

value = ['Attenuation Corrected']
testValue = ['Attenuation Corrected'] (Valid)
= 'Attenuation Corrected' (Valid)
= 'Attenuation' (Fail)
| + | doesNotEqual | !== | Any member is !== for the array, either in value, order, or length | value = ['abc', 'def', 'GHI']
testValue = 'abc' (Valid)
= ['abc'] (Valid)
= ['abc', 'def', 'GHI'] (Fail)
= ['abc', 'GHI', 'def'] (Valid)
= ['abc', 'def'] (Valid)

value = 'Attenuation Corrected'
testValue = 'Attenuation Corrected' (Fail)Valid

= 'Attenuation' (Valid)

value = ['Attenuation Corrected']
testValue = ['Attenuation Corrected'] (Fail)
= 'Attenuation Corrected' (Fail)
= 'Attenuation' (Fail) | + | includes | Not allowed | Value is equal to one of the values of the array | value = ['abc', 'def', 'GHI']
testValue = ['abc'] (Valid)

= ‘abc’ (Fail)

= [‘abc’] (Fail)

= ‘dog’ (Fail)

= = [‘att’, ‘abc’] (Valid)

= ['abc', 'def', 'dog'] (Valid)

= ['cat', 'dog'] (Fail)


value = 'Attenuation Corrected'
testValue = ['Attenuation Corrected', 'Corrected'] (Valid)
= ['Attenuation', 'Corrected'] (Fail)


value = ['Attenuation Corrected']
testValue = 'Attenuation Corrected' (Fail)
= ['Attenuation Corrected', 'Corrected'] (Valid)
= ['Attenuation', 'Corrected'] (Fail) | + | doesNotInclude | Not allowed | Value is not in one of the values of the array | value = ['abc', 'def', 'GHI']
testValue = ‘Corr’ (Valid)

= ‘abc’ (Fail)

= [‘att’, ‘cor’] (Valid)

= ['abc', 'def', 'dog'] (Fail)


value = 'Attenuation Corrected'
testValue = ['Attenuation Corrected', 'Corrected'] (Fail)
= ['Attenuation', 'Corrected'] (Valid)


value = ['Attenuation Corrected']
testValue = 'Attenuation' (Fail)
= ['Attenuation Corrected', 'Corrected'] (Fail)
= ['Attenuation', 'Corrected'] (Valid) | + | containsI | String containment (case insensitive) | String containment (case insensitive) is OK for one of the rule values | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Valid)
= ‘corr’ (Valid)

= [‘att’, ‘cor’] (Valid)

= [‘Att’, ‘Wall’] (Valid)

= [‘cat’, ‘dog’] (Fail)



value = ['abc', 'def', 'GHI']
testValue = 'def' (Valid)

= 'dog' (Fail)

= ['gh', 'de'] (Valid)

= ['cat', 'dog'] (Fail)
| + | contains | String containment (case sensitive) | String containment (case sensitive) is OK for one of the rule values | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Valid)
= ‘corr’ (Fail)
= [‘att’, ‘cor’] (Fail)
= [‘Att’, ‘Wall’] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']

testValue = 'def' (Valid)
= 'dog' (Fail)

= ['cat', 'de'] (Valid)

= ['cat', 'dog'] (Fail) | + | doesNotContain | String containment is false | String containment is false for all values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)


= ‘corr’ (Valid)

= [‘att’, ‘cor’] (Valid)

= [‘Att’, ‘Wall’] (Fail)

= [‘cat’, ‘dog’] (Valid)

value = ['abc', 'def', 'GHI']
testValue = 'def' (Fail)


= 'dog' (Valid)

= ['cat', 'de'] (Fail)

= ['cat', 'dog'] (Valid) | + | doesNotContainI | String containment is false (case insensitive) | String containment (case insensitive) is false for all values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)

= ‘corr’ (Fail)

= [‘att’, ‘cor’] (Fail)

= [‘Att’, ‘Wall’] (Fail)

= [‘cat’, ‘dog’] (Valid)


value = ['abc', 'def', 'GHI']
testValue = 'DEF' (Fail)

= 'dog' (Valid)

= ['cat', 'gh'] (Fail)

= ['cat', 'dog'] (Valid) | + | startsWith | Value begins with characters | Starts with one of the values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)

= ‘Att’ (Fail)

= ['cat', 'dog', 'Att'] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']
testValue = 'deg' (Valid)

= ['cat', 'GH'] (Valid)

= ['cat', 'gh'] (Fail)

= ['cat', 'dog'] (Fail) | + | endsWith | Value ends with characters | ends with one of the value of the array | value = 'Attenuation Corrected'
testValue = ‘TED’ (Fail)

= ‘ted’ (Valid)

= ['cat', 'dog', 'ted'] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']
testValue = 'deg' (Valid)

= ['cat', 'HI'] (Valid)

= ['cat', 'hi'] (Fail)

= ['cat', 'dog'] (Fail) | + | greaterThan | value is => to rule | Not applicable | value = 30

testValue = 20 (Valid)
= 40 (Fail)

| + | lessThan | value is <= to rule | Not applicable | value = 30

testValue = 40 (Valid)
= 20 (Fail)
| + | range | Not applicable | 2 value requested (min and max) | value = 50

testValue = [10,60] (Valid)
= [60, 10] (Valid)

= [0, 10] (Fail)

= [70, 80] (Fail)

= 45 (Fail)

= [45] (Fail) | + | notNull | Not Applicable | Not Applicable | No value | A sample of the matching rule is above which matches against the study description to be PETCT ```js