Skip to content

Commit

Permalink
fixing ability to do numeric/date entry + NullValueMenu
Browse files Browse the repository at this point in the history
  • Loading branch information
ddooley committed Dec 13, 2024
1 parent 569b947 commit a5251b6
Showing 1 changed file with 79 additions and 39 deletions.
118 changes: 79 additions & 39 deletions lib/Validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,13 @@ class Validator {
if (!todos || !todos.length) {
return;
}
// todos is an array of strings.
const slotType = this.getSlotType(slotDefinition);// LinkML schema.type object

const slotType = this.getSlotType(slotDefinition);

// Slot type could be:
// a number, string, date ...
// null - if it is only a menu
// both date and NullValueList
if (slotType?.uri === 'xsd:date') {
for (const todo of todos) {
if (todo.substring(0, 2) === '>=') {
Expand All @@ -70,16 +74,11 @@ class Validator {
}
}

for (const def of slotDefinition.any_of || []) {
processTodos(def, todos);
}
for (const def of slotDefinition.all_of || []) {
processTodos(def, todos);
}
for (const def of slotDefinition.exactly_one_of || []) {
processTodos(def, todos);
}
for (const def of slotDefinition.none_of || []) {
// Cycle through each slotDefinition any_of etc. object entries and get
// the datatype of its .range (or recurse) and in LinkML fashion attach
// minimum_value and maximum_value to the slotDefinition OR its any_of
// etc array BASED ON top level todos. E.g. inheriting min/max criteria.
for (const def of slotDefinition.any_of ?? slotDefinition.all_of ?? slotDefinition.exactly_one_of ?? slotDefinition.none_of ?? []) {
processTodos(def, todos);
}
};
Expand All @@ -94,13 +93,15 @@ class Validator {
*/
this.#dependantMinimumValuesMap = new Map();
this.#dependantMaximumValuesMap = new Map();
const RE_TODOS = new RegExp(/^([><])={(.*?)}$/);

for (const slotDefinition of Object.values(this.#targetClassInducedSlots)) {
const { todos } = slotDefinition;
if (!todos || !todos.length) {
continue;
}
for (const todo of todos) {
const match = todo.match(/^([><])={(.*?)}$/);
const match = todo.match(RE_TODOS);
if (match == null) {
continue;
}
Expand Down Expand Up @@ -140,7 +141,7 @@ class Validator {
this.#valueValidatorMap = new Map();
}

/* This returns a single primitve data type for a slot - a decimal, date,
/* This returns a single primitive data type for a slot - a decimal, date,
string etc. or possibly an enumeration. Enumerations are handled
separately however (by const slotEnum = ...). Slots either use "range"
attribute, OR they use any_of or exactly_one_of etc. attribute expression
Expand All @@ -164,6 +165,12 @@ class Validator {
return slotType;
}

/* A validation function is prepared and cached for every slot presented, so
that validation throug rows of data can make use of already established
validator for each column. Particular columns may have sensitivity to other
column values (via min/max references) or special {today} e.g. so
*/
getValidatorForSlot(slot, options = {}) {
const { cacheKey, inheritedRange } = options;
if (typeof cacheKey === 'string' && this.#valueValidatorMap.has(cacheKey)) {
Expand All @@ -177,11 +184,13 @@ class Validator {
slotDefinition = slot;
}

// This digs down into any_of, all_of etc to find first date, number etc.
// slotType is LinkML schema.type object
if (!slotDefinition.range && inheritedRange) {
slotDefinition.range = inheritedRange;
}

const slotType = this.getSlotType(slotDefinition);
const slotType = this.getSlotType(slotDefinition); // LinkML schema.type object

const slotEnum = this.#schema.enums?.[slotDefinition.range];
const slotPermissibleValues = Object.values(
Expand All @@ -194,11 +203,13 @@ class Validator {
// TEST CASE:
// if (slotDefinition.name == "sample_collection_date")
// console.log("any_of", DEBUG INFO)
// inheritedRange comes from original slot, so it might be a date or number + menu
const anyOfValidators = (slotDefinition.any_of ?? []).map((subSlot) =>
this.getValidatorForSlot(subSlot, {
inheritedRange: slotDefinition.range,
})
);

const allOfValidators = (slotDefinition.all_of ?? []).map((subSlot) =>
this.getValidatorForSlot(subSlot, {
inheritedRange: slotDefinition.range,
Expand Down Expand Up @@ -239,6 +250,7 @@ class Validator {
if (!value) {
if (slotDefinition.required)
return 'This field is required';
// value_presence is subject to dynamic rules?
if (slotDefinition.value_presence === 'PRESENT')
return 'Value is not present';
return;
Expand Down Expand Up @@ -266,39 +278,62 @@ class Validator {
splitValues = [value];
}

// For any of the value(s), whether they are valid depends on either
// an ok primitive data type parsing, OR a categorical menu match.
// Message needs
for (const value of splitValues) {
if (slotType) {
let parse_error = false;
if (slotType) {// Doesn't pertain to slots which are ONLY enumerations.
const parsed = this.#parser.parse(value, slotType.uri);

// Issue: parse can fail on decimal but menu has "Missing"
if (parsed === undefined) {
return `Value does not match format for ${slotType.uri}`;
}
parse_error = `Value does not match format for ${slotType.uri}`;

if (slotMinimumValue !== undefined && parsed < slotMinimumValue) {
return 'Value is less than minimum value';
//if (!(anyOfValidators.length || allOfValidators.length || exactlyOneOfValidators.length || noneOfValidators.length)) {
// return parse_error;
//}
}
// All these cases have encountered an item which matches basic data
// datatype and so sudden death is ok.
else {

if (slotMaximumValue !== undefined && parsed > slotMaximumValue) {
return 'Value is greater than maximum value';
}
if (slotMinimumValue !== undefined && parsed < slotMinimumValue) {
return 'Value is less than minimum value';
}

if (slotMaximumValue !== undefined && parsed > slotMaximumValue) {
return 'Value is greater than maximum value';
}

if (
(slotDefinition.equals_string !== undefined &&
parsed !== slotDefinition.equals_string) ||
(slotDefinition.equals_number !== undefined &&
parsed !== slotDefinition.equals_number)
) {
return 'Value does not match constant';
}

if (
(slotDefinition.equals_string !== undefined &&
parsed !== slotDefinition.equals_string) ||
(slotDefinition.equals_number !== undefined &&
parsed !== slotDefinition.equals_number)
) {
return 'Value does not match constant';
}
if (
slotDefinition.pattern !== undefined &&
!value.match(slotDefinition.pattern)
) {
return 'Value does not match pattern';
}

if (
slotDefinition.pattern !== undefined &&
!value.match(slotDefinition.pattern)
) {
return 'Value does not match pattern';
// Here slotType value tested and is ok!
continue;
}

// Here value didn't parse to slotType

}
else {
// No basic slot type here so only enumeration handling.
}

// Single range for slot given so no need to evaluate all_of, any_of etc.
if (slotEnum && !slotPermissibleValues.includes(value)) {
return 'Value is not allowed';
}
Expand Down Expand Up @@ -342,12 +377,17 @@ class Validator {
return 'One or more expressions of none_of held';
}
}

if (anyOfValidators.length || allOfValidators.length || exactlyOneOfValidators.length || noneOfValidators.length) {
// We passed validation here which means a parse error can be overriden
}
else if (parse_error.length) {
//There were no other ranges besides basic slotType so
return parse_error;
}
}
};

if (typeof cacheKey === 'string') {
this.#valueValidatorMap.set(cacheKey, validate);
}

return validate;
}
Expand Down

0 comments on commit a5251b6

Please sign in to comment.