refactor: extract validators from validationUtils.js into strategy pattern modules
Break the 548-line monolith into focused modules: - validators/typeValidators.js (number, integer, boolean, string, enum) - validators/collectionValidators.js (array, set, object) - validators/curveValidator.js (curve, machineCurve, dimensionStructure) validationUtils.js now uses a VALIDATORS registry map and delegates to extracted modules. Reduced from 548 to 217 lines. Closes #2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,11 +28,25 @@
|
|||||||
* @module ValidationUtils
|
* @module ValidationUtils
|
||||||
* @requires Logger
|
* @requires Logger
|
||||||
* @exports ValidationUtils
|
* @exports ValidationUtils
|
||||||
* @version 0.1.0
|
* @version 0.2.0
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Logger = require("./logger");
|
const Logger = require("./logger");
|
||||||
|
const { validateNumber, validateInteger, validateBoolean, validateString, validateEnum } = require("./validators/typeValidators");
|
||||||
|
const { validateArray, validateSet, validateObject } = require("./validators/collectionValidators");
|
||||||
|
const { validateCurve, validateMachineCurve } = require("./validators/curveValidator");
|
||||||
|
|
||||||
|
// Strategy registry: maps rules.type to a handler function
|
||||||
|
const VALIDATORS = {
|
||||||
|
number: (cv, rules, fs, name, key, logger) => validateNumber(cv, rules, fs, name, key, logger),
|
||||||
|
integer: (cv, rules, fs, name, key, logger) => validateInteger(cv, rules, fs, name, key, logger),
|
||||||
|
boolean: (cv, _rules, _fs, name, key, logger) => validateBoolean(cv, name, key, logger),
|
||||||
|
string: (cv, rules, fs, name, key, logger) => validateString(cv, rules, fs, name, key, logger),
|
||||||
|
enum: (cv, rules, fs, name, key, logger) => validateEnum(cv, rules, fs, name, key, logger),
|
||||||
|
array: (cv, rules, fs, name, key, logger) => validateArray(cv, rules, fs, name, key, logger),
|
||||||
|
set: (cv, rules, fs, name, key, logger) => validateSet(cv, rules, fs, name, key, logger),
|
||||||
|
};
|
||||||
|
|
||||||
class ValidationUtils {
|
class ValidationUtils {
|
||||||
constructor(IloggerEnabled, IloggerLevel) {
|
constructor(IloggerEnabled, IloggerLevel) {
|
||||||
@@ -105,63 +119,44 @@ class ValidationUtils {
|
|||||||
configValue = config[key] !== undefined ? config[key] : fieldSchema.default;
|
configValue = config[key] !== undefined ? config[key] : fieldSchema.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to parse the value to the expected type if possible
|
// Handle curve types (they use continue, so handle separately)
|
||||||
switch (rules.type) {
|
if (rules.type === "curve") {
|
||||||
|
validatedConfig[key] = validateCurve(configValue, fieldSchema.default, this.logger);
|
||||||
case "number":
|
|
||||||
configValue = this.validateNumber(configValue, rules, fieldSchema, name, key);
|
|
||||||
break;
|
|
||||||
case "boolean":
|
|
||||||
configValue = this.validateBoolean(configValue, name, key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "string":
|
|
||||||
configValue = this.validateString(configValue,rules,fieldSchema, name, key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "array":
|
|
||||||
configValue = this.validateArray(configValue, rules, fieldSchema, name, key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "set":
|
|
||||||
configValue = this.validateSet(configValue, rules, fieldSchema, name, key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "object":
|
|
||||||
configValue = this.validateObject(configValue, rules, fieldSchema, name, key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "enum":
|
|
||||||
configValue = this.validateEnum(configValue, rules, fieldSchema, name, key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "curve":
|
|
||||||
validatedConfig[key] = this.validateCurve(configValue,fieldSchema.default);
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
case "machineCurve":
|
if (rules.type === "machineCurve") {
|
||||||
validatedConfig[key] = this.validateMachineCurve(configValue,fieldSchema.default);
|
validatedConfig[key] = validateMachineCurve(configValue, fieldSchema.default, this.logger);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
case "integer":
|
// Handle object type (needs recursive validateSchema reference)
|
||||||
validatedConfig[key] = this.validateInteger(configValue, rules, fieldSchema, name, key);
|
if (rules.type === "object") {
|
||||||
|
validatedConfig[key] = validateObject(
|
||||||
|
configValue, rules, fieldSchema, name, key,
|
||||||
|
(c, s, n) => this.validateSchema(c, s, n),
|
||||||
|
this.logger
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
case undefined:
|
// Handle undefined type
|
||||||
// If we see 'rules.schema' but no 'rules.type', treat it like an object:
|
if (rules.type === undefined) {
|
||||||
if (rules.schema && !rules.type) {
|
if (rules.schema && !rules.type) {
|
||||||
// Log a warning and skip the extra pass for nested schema
|
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`${name}.${key} has a nested schema but no type. ` +
|
`${name}.${key} has a nested schema but no type. ` +
|
||||||
`Treating it as type="object" to skip extra pass.`
|
`Treating it as type="object" to skip extra pass.`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, fallback to your existing "validateUndefined" logic
|
|
||||||
validatedConfig[key] = this.validateUndefined(configValue, fieldSchema, name, key);
|
validatedConfig[key] = this.validateUndefined(configValue, fieldSchema, name, key);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
// Use the strategy registry for all other types
|
||||||
|
const handler = VALIDATORS[rules.type];
|
||||||
|
if (handler) {
|
||||||
|
configValue = handler(configValue, rules, fieldSchema, name, key, this.logger);
|
||||||
|
} else {
|
||||||
this.logger.warn(`${name}.${key} has an unknown validation type: ${rules.type}. Skipping validation.`);
|
this.logger.warn(`${name}.${key} has an unknown validation type: ${rules.type}. Skipping validation.`);
|
||||||
validatedConfig[key] = fieldSchema.default;
|
validatedConfig[key] = fieldSchema.default;
|
||||||
continue;
|
continue;
|
||||||
@@ -204,332 +199,6 @@ class ValidationUtils {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
validateMachineCurve(curve, defaultCurve) {
|
|
||||||
if (!curve || typeof curve !== "object" || Object.keys(curve).length === 0) {
|
|
||||||
this.logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
|
|
||||||
return defaultCurve;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that nq and np exist and are objects
|
|
||||||
const { nq, np } = curve;
|
|
||||||
if (!nq || typeof nq !== "object" || !np || typeof np !== "object") {
|
|
||||||
this.logger.warn("Curve must contain valid 'nq' and 'np' objects. Defaulting to basic curve.");
|
|
||||||
return defaultCurve;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that each dimension key points to a valid object with x and y arrays
|
|
||||||
const validatedNq = this.validateDimensionStructure(nq, "nq");
|
|
||||||
const validatedNp = this.validateDimensionStructure(np, "np");
|
|
||||||
|
|
||||||
if (!validatedNq || !validatedNp) {
|
|
||||||
return defaultCurve;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { nq: validatedNq, np: validatedNp }; // Return the validated curve
|
|
||||||
}
|
|
||||||
|
|
||||||
validateCurve(curve, defaultCurve) {
|
|
||||||
if (!curve || typeof curve !== "object" || Object.keys(curve).length === 0) {
|
|
||||||
this.logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
|
|
||||||
return defaultCurve;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that each dimension key points to a valid object with x and y arrays
|
|
||||||
const validatedCurve = this.validateDimensionStructure(curve, "curve");
|
|
||||||
if (!validatedCurve) {
|
|
||||||
return defaultCurve;
|
|
||||||
}
|
|
||||||
|
|
||||||
return validatedCurve; // Return the validated curve
|
|
||||||
}
|
|
||||||
|
|
||||||
validateDimensionStructure(dimension, name) {
|
|
||||||
const validatedDimension = {};
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(dimension)) {
|
|
||||||
// Validate that each key points to an object with x and y arrays
|
|
||||||
if (typeof value !== "object") {
|
|
||||||
this.logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Validate that x and y are arrays
|
|
||||||
else if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
|
|
||||||
this.logger.warn(`Dimension '${name}' key '${key}' is missing x or y arrays. Converting to arrays.`);
|
|
||||||
// Try to convert to arrays first
|
|
||||||
value.x = Object.values(value.x);
|
|
||||||
value.y = Object.values(value.y);
|
|
||||||
|
|
||||||
// If still not arrays return false
|
|
||||||
if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
|
|
||||||
this.logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Validate that x and y arrays are the same length
|
|
||||||
else if (value.x.length !== value.y.length) {
|
|
||||||
this.logger.warn(`Dimension '${name}' key '${key}' has mismatched x and y lengths. Ignoring this key.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Validate that x values are in ascending order — sort if not
|
|
||||||
else if (!this.isSorted(value.x)) {
|
|
||||||
this.logger.warn(`Dimension '${name}' key '${key}' has unsorted x values. Sorting...`);
|
|
||||||
const indices = value.x.map((v, i) => i);
|
|
||||||
indices.sort((a, b) => value.x[a] - value.x[b]);
|
|
||||||
value.x = indices.map(i => value.x[i]);
|
|
||||||
value.y = indices.map(i => value.y[i]);
|
|
||||||
}
|
|
||||||
// Validate that x values are unique — remove duplicates if not
|
|
||||||
if (!this.isUnique(value.x)) {
|
|
||||||
this.logger.warn(`Dimension '${name}' key '${key}' has duplicate x values. Removing duplicates...`);
|
|
||||||
const seen = new Set();
|
|
||||||
const uniqueX = [];
|
|
||||||
const uniqueY = [];
|
|
||||||
for (let i = 0; i < value.x.length; i++) {
|
|
||||||
if (!seen.has(value.x[i])) {
|
|
||||||
seen.add(value.x[i]);
|
|
||||||
uniqueX.push(value.x[i]);
|
|
||||||
uniqueY.push(value.y[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value.x = uniqueX;
|
|
||||||
value.y = uniqueY;
|
|
||||||
}
|
|
||||||
// Validate that y values are numbers
|
|
||||||
if (!this.areNumbers(value.y)) {
|
|
||||||
this.logger.warn(`Dimension '${name}' key '${key}' has non-numeric y values. Ignoring this key.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
validatedDimension[key] = value;
|
|
||||||
}
|
|
||||||
return validatedDimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSorted(arr) {
|
|
||||||
return arr.every((_, i) => i === 0 || arr[i] >= arr[i - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
isUnique(arr) {
|
|
||||||
return new Set(arr).size === arr.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
areNumbers(arr) {
|
|
||||||
return arr.every((x) => typeof x === "number");
|
|
||||||
}
|
|
||||||
|
|
||||||
validateNumber(configValue, rules, fieldSchema, name, key) {
|
|
||||||
|
|
||||||
if (typeof configValue !== "number") {
|
|
||||||
const parsedValue = parseFloat(configValue);
|
|
||||||
if (!isNaN(parsedValue)) {
|
|
||||||
this.logger.warn(`${name}.${key} was parsed to a number: ${configValue} -> ${parsedValue}`);
|
|
||||||
configValue = parsedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rules.min !== undefined && configValue < rules.min) {
|
|
||||||
this.logger.warn(
|
|
||||||
`${name}.${key} is below the minimum (${rules.min}). Using default value.`
|
|
||||||
);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
if (rules.max !== undefined && configValue > rules.max) {
|
|
||||||
this.logger.warn(
|
|
||||||
`${name}.${key} exceeds the maximum (${rules.max}). Using default value.`
|
|
||||||
);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.debug(`${name}.${key} is a valid number: ${configValue}`);
|
|
||||||
|
|
||||||
return configValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
validateInteger(configValue, rules, fieldSchema, name, key) {
|
|
||||||
if (typeof configValue !== "number" || !Number.isInteger(configValue)) {
|
|
||||||
const parsedValue = parseInt(configValue, 10);
|
|
||||||
if (!isNaN(parsedValue) && Number.isInteger(parsedValue)) {
|
|
||||||
this.logger.warn(`${name}.${key} was parsed to an integer: ${configValue} -> ${parsedValue}`);
|
|
||||||
configValue = parsedValue;
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`${name}.${key} is not a valid integer. Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rules.min !== undefined && configValue < rules.min) {
|
|
||||||
this.logger.warn(`${name}.${key} is below the minimum integer value (${rules.min}). Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rules.max !== undefined && configValue > rules.max) {
|
|
||||||
this.logger.warn(`${name}.${key} exceeds the maximum integer value (${rules.max}). Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.debug(`${name}.${key} is a valid integer: ${configValue}`);
|
|
||||||
return configValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateBoolean(configValue, name, key) {
|
|
||||||
if (typeof configValue !== "boolean") {
|
|
||||||
if (configValue === "true" || configValue === "false") {
|
|
||||||
const parsedValue = configValue === "true";
|
|
||||||
this.logger.debug(`${name}.${key} was parsed to a boolean: ${configValue} -> ${parsedValue}`);
|
|
||||||
configValue = parsedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return configValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateString(configValue, rules, fieldSchema, name, key) {
|
|
||||||
let newConfigValue = configValue;
|
|
||||||
|
|
||||||
if (typeof configValue !== "string") {
|
|
||||||
//check if the value is nullable
|
|
||||||
if(rules.nullable){
|
|
||||||
if(configValue === null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.warn(`${name}.${key} is not a string. Trying to convert to string.`);
|
|
||||||
newConfigValue = String(configValue); // Coerce to string if not already
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if the string is a valid string after conversion
|
|
||||||
if (typeof newConfigValue !== "string") {
|
|
||||||
this.logger.warn(`${name}.${key} is not a valid string. Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for uppercase characters and convert to lowercase if present
|
|
||||||
if (newConfigValue !== newConfigValue.toLowerCase()) {
|
|
||||||
this.logger.warn(`${name}.${key} contains uppercase characters. Converting to lowercase: ${newConfigValue} -> ${newConfigValue.toLowerCase()}`);
|
|
||||||
newConfigValue = newConfigValue.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
return newConfigValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateSet(configValue, rules, fieldSchema, name, key) {
|
|
||||||
// 1. Ensure we have a Set. If not, use default.
|
|
||||||
if (!(configValue instanceof Set)) {
|
|
||||||
this.logger.info(`${name}.${key} is not a Set. Converting to one using default value.`);
|
|
||||||
return new Set(fieldSchema.default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Convert the Set to an array for easier filtering.
|
|
||||||
const validatedArray = [...configValue]
|
|
||||||
.filter((item) => {
|
|
||||||
// 3. Filter based on `rules.itemType`.
|
|
||||||
switch (rules.itemType) {
|
|
||||||
case "number":
|
|
||||||
return typeof item === "number";
|
|
||||||
case "string":
|
|
||||||
return typeof item === "string";
|
|
||||||
case "null":
|
|
||||||
// "null" might mean no type restriction (your usage may vary).
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
// Fallback if itemType is something else
|
|
||||||
return typeof item === rules.itemType;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.slice(0, rules.maxLength || Infinity);
|
|
||||||
|
|
||||||
// 4. Check if the filtered array meets the minimum length.
|
|
||||||
if (validatedArray.length < (rules.minLength || 1)) {
|
|
||||||
this.logger.warn(
|
|
||||||
`${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
|
|
||||||
);
|
|
||||||
return new Set(fieldSchema.default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Return a new Set containing only the valid items.
|
|
||||||
return new Set(validatedArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
validateArray(configValue, rules, fieldSchema, name, key) {
|
|
||||||
if (!Array.isArray(configValue)) {
|
|
||||||
this.logger.info(`${name}.${key} is not an array. Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate individual items in the array
|
|
||||||
const validatedArray = configValue
|
|
||||||
.filter((item) => {
|
|
||||||
switch (rules.itemType) {
|
|
||||||
case "number":
|
|
||||||
return typeof item === "number";
|
|
||||||
case "string":
|
|
||||||
return typeof item === "string";
|
|
||||||
case "null":
|
|
||||||
// anything goes
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return typeof item === rules.itemType;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.slice(0, rules.maxLength || Infinity);
|
|
||||||
|
|
||||||
if (validatedArray.length < (rules.minLength || 1)) {
|
|
||||||
this.logger.warn(
|
|
||||||
`${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
|
|
||||||
);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return validatedArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateObject(configValue, rules, fieldSchema, name, key) {
|
|
||||||
if (typeof configValue !== "object" || Array.isArray(configValue)) {
|
|
||||||
this.logger.warn(`${name}.${key} is not a valid object. Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rules.schema) {
|
|
||||||
// Recursively validate nested objects if a schema is defined
|
|
||||||
return this.validateSchema(configValue || {}, rules.schema, `${name}.${key}`);
|
|
||||||
} else {
|
|
||||||
// If no schema is defined, log a warning and use the default
|
|
||||||
this.logger.warn(`${name}.${key} is an object with no schema. Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validateEnum(configValue, rules, fieldSchema, name, key) {
|
|
||||||
|
|
||||||
if (Array.isArray(rules.values)) {
|
|
||||||
|
|
||||||
//if value is null take default
|
|
||||||
if(configValue === null){
|
|
||||||
this.logger.warn(`${name}.${key} is null. Using default value.`);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validValues = rules.values.map(e => e.value.toLowerCase());
|
|
||||||
|
|
||||||
//remove caps
|
|
||||||
configValue = configValue.toLowerCase();
|
|
||||||
|
|
||||||
if (!validValues.includes(configValue)) {
|
|
||||||
this.logger.warn(
|
|
||||||
`${name}.${key} has an invalid value : ${configValue}. Allowed values: [${validValues.join(", ")}]. Using default value.`
|
|
||||||
);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.logger.warn(
|
|
||||||
`${name}.${key} is an enum with no 'values' array. Using default value.`
|
|
||||||
);
|
|
||||||
return fieldSchema.default;
|
|
||||||
}
|
|
||||||
return configValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateUndefined(configValue, fieldSchema, name, key) {
|
validateUndefined(configValue, fieldSchema, name, key) {
|
||||||
if (typeof configValue === "object" && !Array.isArray(configValue)) {
|
if (typeof configValue === "object" && !Array.isArray(configValue)) {
|
||||||
|
|
||||||
|
|||||||
66
src/helper/validators/collectionValidators.js
Normal file
66
src/helper/validators/collectionValidators.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Standalone collection validation functions extracted from validationUtils.js.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function validateArray(configValue, rules, fieldSchema, name, key, logger) {
|
||||||
|
if (!Array.isArray(configValue)) {
|
||||||
|
logger.info(`${name}.${key} is not an array. Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
const validatedArray = configValue
|
||||||
|
.filter((item) => {
|
||||||
|
switch (rules.itemType) {
|
||||||
|
case "number": return typeof item === "number";
|
||||||
|
case "string": return typeof item === "string";
|
||||||
|
case "null": return true;
|
||||||
|
default: return typeof item === rules.itemType;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.slice(0, rules.maxLength || Infinity);
|
||||||
|
if (validatedArray.length < (rules.minLength || 1)) {
|
||||||
|
logger.warn(
|
||||||
|
`${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
|
||||||
|
);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
return validatedArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSet(configValue, rules, fieldSchema, name, key, logger) {
|
||||||
|
if (!(configValue instanceof Set)) {
|
||||||
|
logger.info(`${name}.${key} is not a Set. Converting to one using default value.`);
|
||||||
|
return new Set(fieldSchema.default);
|
||||||
|
}
|
||||||
|
const validatedArray = [...configValue]
|
||||||
|
.filter((item) => {
|
||||||
|
switch (rules.itemType) {
|
||||||
|
case "number": return typeof item === "number";
|
||||||
|
case "string": return typeof item === "string";
|
||||||
|
case "null": return true;
|
||||||
|
default: return typeof item === rules.itemType;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.slice(0, rules.maxLength || Infinity);
|
||||||
|
if (validatedArray.length < (rules.minLength || 1)) {
|
||||||
|
logger.warn(
|
||||||
|
`${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
|
||||||
|
);
|
||||||
|
return new Set(fieldSchema.default);
|
||||||
|
}
|
||||||
|
return new Set(validatedArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateObject(configValue, rules, fieldSchema, name, key, validateSchemaFn, logger) {
|
||||||
|
if (typeof configValue !== "object" || Array.isArray(configValue)) {
|
||||||
|
logger.warn(`${name}.${key} is not a valid object. Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
if (rules.schema) {
|
||||||
|
return validateSchemaFn(configValue || {}, rules.schema, `${name}.${key}`);
|
||||||
|
} else {
|
||||||
|
logger.warn(`${name}.${key} is an object with no schema. Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { validateArray, validateSet, validateObject };
|
||||||
108
src/helper/validators/curveValidator.js
Normal file
108
src/helper/validators/curveValidator.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* Curve validation strategies for machine curves and generic curves.
|
||||||
|
* Extracted from validationUtils.js for modularity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isSorted(arr) {
|
||||||
|
return arr.every((_, i) => i === 0 || arr[i] >= arr[i - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUnique(arr) {
|
||||||
|
return new Set(arr).size === arr.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function areNumbers(arr) {
|
||||||
|
return arr.every((x) => typeof x === "number");
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDimensionStructure(dimension, name, logger) {
|
||||||
|
const validatedDimension = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(dimension)) {
|
||||||
|
if (typeof value !== "object") {
|
||||||
|
logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
|
||||||
|
logger.warn(`Dimension '${name}' key '${key}' is missing x or y arrays. Converting to arrays.`);
|
||||||
|
value.x = Object.values(value.x);
|
||||||
|
value.y = Object.values(value.y);
|
||||||
|
if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
|
||||||
|
logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value.x.length !== value.y.length) {
|
||||||
|
logger.warn(`Dimension '${name}' key '${key}' has mismatched x and y lengths. Ignoring this key.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (!isSorted(value.x)) {
|
||||||
|
logger.warn(`Dimension '${name}' key '${key}' has unsorted x values. Sorting...`);
|
||||||
|
const indices = value.x.map((_v, i) => i);
|
||||||
|
indices.sort((a, b) => value.x[a] - value.x[b]);
|
||||||
|
value.x = indices.map(i => value.x[i]);
|
||||||
|
value.y = indices.map(i => value.y[i]);
|
||||||
|
}
|
||||||
|
if (!isUnique(value.x)) {
|
||||||
|
logger.warn(`Dimension '${name}' key '${key}' has duplicate x values. Removing duplicates...`);
|
||||||
|
const seen = new Set();
|
||||||
|
const uniqueX = [];
|
||||||
|
const uniqueY = [];
|
||||||
|
for (let i = 0; i < value.x.length; i++) {
|
||||||
|
if (!seen.has(value.x[i])) {
|
||||||
|
seen.add(value.x[i]);
|
||||||
|
uniqueX.push(value.x[i]);
|
||||||
|
uniqueY.push(value.y[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.x = uniqueX;
|
||||||
|
value.y = uniqueY;
|
||||||
|
}
|
||||||
|
if (!areNumbers(value.y)) {
|
||||||
|
logger.warn(`Dimension '${name}' key '${key}' has non-numeric y values. Ignoring this key.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
validatedDimension[key] = value;
|
||||||
|
}
|
||||||
|
return validatedDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCurve(configValue, defaultCurve, logger) {
|
||||||
|
if (!configValue || typeof configValue !== "object" || Object.keys(configValue).length === 0) {
|
||||||
|
logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
|
||||||
|
return defaultCurve;
|
||||||
|
}
|
||||||
|
const validatedCurve = validateDimensionStructure(configValue, "curve", logger);
|
||||||
|
if (!validatedCurve) {
|
||||||
|
return defaultCurve;
|
||||||
|
}
|
||||||
|
return validatedCurve;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateMachineCurve(configValue, defaultCurve, logger) {
|
||||||
|
if (!configValue || typeof configValue !== "object" || Object.keys(configValue).length === 0) {
|
||||||
|
logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
|
||||||
|
return defaultCurve;
|
||||||
|
}
|
||||||
|
const { nq, np } = configValue;
|
||||||
|
if (!nq || typeof nq !== "object" || !np || typeof np !== "object") {
|
||||||
|
logger.warn("Curve must contain valid 'nq' and 'np' objects. Defaulting to basic curve.");
|
||||||
|
return defaultCurve;
|
||||||
|
}
|
||||||
|
const validatedNq = validateDimensionStructure(nq, "nq", logger);
|
||||||
|
const validatedNp = validateDimensionStructure(np, "np", logger);
|
||||||
|
if (!validatedNq || !validatedNp) {
|
||||||
|
return defaultCurve;
|
||||||
|
}
|
||||||
|
return { nq: validatedNq, np: validatedNp };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateCurve,
|
||||||
|
validateMachineCurve,
|
||||||
|
validateDimensionStructure,
|
||||||
|
isSorted,
|
||||||
|
isUnique,
|
||||||
|
areNumbers
|
||||||
|
};
|
||||||
110
src/helper/validators/typeValidators.js
Normal file
110
src/helper/validators/typeValidators.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Standalone type validation functions extracted from validationUtils.js.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function validateNumber(configValue, rules, fieldSchema, name, key, logger) {
|
||||||
|
if (typeof configValue !== "number") {
|
||||||
|
const parsedValue = parseFloat(configValue);
|
||||||
|
if (!isNaN(parsedValue)) {
|
||||||
|
logger.warn(`${name}.${key} was parsed to a number: ${configValue} -> ${parsedValue}`);
|
||||||
|
configValue = parsedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rules.min !== undefined && configValue < rules.min) {
|
||||||
|
logger.warn(`${name}.${key} is below the minimum (${rules.min}). Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
if (rules.max !== undefined && configValue > rules.max) {
|
||||||
|
logger.warn(`${name}.${key} exceeds the maximum (${rules.max}). Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
logger.debug(`${name}.${key} is a valid number: ${configValue}`);
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateInteger(configValue, rules, fieldSchema, name, key, logger) {
|
||||||
|
if (typeof configValue !== "number" || !Number.isInteger(configValue)) {
|
||||||
|
const parsedValue = parseInt(configValue, 10);
|
||||||
|
if (!isNaN(parsedValue) && Number.isInteger(parsedValue)) {
|
||||||
|
logger.warn(`${name}.${key} was parsed to an integer: ${configValue} -> ${parsedValue}`);
|
||||||
|
configValue = parsedValue;
|
||||||
|
} else {
|
||||||
|
logger.warn(`${name}.${key} is not a valid integer. Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rules.min !== undefined && configValue < rules.min) {
|
||||||
|
logger.warn(`${name}.${key} is below the minimum integer value (${rules.min}). Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
if (rules.max !== undefined && configValue > rules.max) {
|
||||||
|
logger.warn(`${name}.${key} exceeds the maximum integer value (${rules.max}). Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
logger.debug(`${name}.${key} is a valid integer: ${configValue}`);
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateBoolean(configValue, name, key, logger) {
|
||||||
|
if (typeof configValue !== "boolean") {
|
||||||
|
if (configValue === "true" || configValue === "false") {
|
||||||
|
const parsedValue = configValue === "true";
|
||||||
|
logger.debug(`${name}.${key} was parsed to a boolean: ${configValue} -> ${parsedValue}`);
|
||||||
|
configValue = parsedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateString(configValue, rules, fieldSchema, name, key, logger) {
|
||||||
|
let newConfigValue = configValue;
|
||||||
|
if (typeof configValue !== "string") {
|
||||||
|
if(rules.nullable){
|
||||||
|
if(configValue === null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.warn(`${name}.${key} is not a string. Trying to convert to string.`);
|
||||||
|
newConfigValue = String(configValue);
|
||||||
|
}
|
||||||
|
if (typeof newConfigValue !== "string") {
|
||||||
|
logger.warn(`${name}.${key} is not a valid string. Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
if (newConfigValue !== newConfigValue.toLowerCase()) {
|
||||||
|
logger.warn(`${name}.${key} contains uppercase characters. Converting to lowercase: ${newConfigValue} -> ${newConfigValue.toLowerCase()}`);
|
||||||
|
newConfigValue = newConfigValue.toLowerCase();
|
||||||
|
}
|
||||||
|
return newConfigValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEnum(configValue, rules, fieldSchema, name, key, logger) {
|
||||||
|
if (Array.isArray(rules.values)) {
|
||||||
|
if(configValue === null){
|
||||||
|
logger.warn(`${name}.${key} is null. Using default value.`);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
const validValues = rules.values.map(e => e.value.toLowerCase());
|
||||||
|
configValue = configValue.toLowerCase();
|
||||||
|
if (!validValues.includes(configValue)) {
|
||||||
|
logger.warn(
|
||||||
|
`${name}.${key} has an invalid value : ${configValue}. Allowed values: [${validValues.join(", ")}]. Using default value.`
|
||||||
|
);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
`${name}.${key} is an enum with no 'values' array. Using default value.`
|
||||||
|
);
|
||||||
|
return fieldSchema.default;
|
||||||
|
}
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateNumber,
|
||||||
|
validateInteger,
|
||||||
|
validateBoolean,
|
||||||
|
validateString,
|
||||||
|
validateEnum,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user