From fe2631f29bb6f83eae11638eeca2224c42876324 Mon Sep 17 00:00:00 2001 From: Rene De Ren Date: Wed, 11 Mar 2026 15:15:01 +0100 Subject: [PATCH] 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 --- src/helper/validationUtils.js | 485 +++--------------- src/helper/validators/collectionValidators.js | 66 +++ src/helper/validators/curveValidator.js | 108 ++++ src/helper/validators/typeValidators.js | 110 ++++ 4 files changed, 361 insertions(+), 408 deletions(-) create mode 100644 src/helper/validators/collectionValidators.js create mode 100644 src/helper/validators/curveValidator.js create mode 100644 src/helper/validators/typeValidators.js diff --git a/src/helper/validationUtils.js b/src/helper/validationUtils.js index 8c2dc33..d87c033 100644 --- a/src/helper/validationUtils.js +++ b/src/helper/validationUtils.js @@ -1,38 +1,52 @@ /** * @file validation.js * - * Permission is hereby granted to any person obtaining a copy of this software - * and associated documentation files (the "Software"), to use it for personal + * Permission is hereby granted to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to use it for personal * or non-commercial purposes, with the following restrictions: * - * 1. **No Copying or Redistribution**: The Software or any of its parts may not - * be copied, merged, distributed, sublicensed, or sold without explicit + * 1. **No Copying or Redistribution**: The Software or any of its parts may not + * be copied, merged, distributed, sublicensed, or sold without explicit * prior written permission from the author. - * - * 2. **Commercial Use**: Any use of the Software for commercial purposes requires + * + * 2. **Commercial Use**: Any use of the Software for commercial purposes requires * a valid license, obtainable only with the explicit consent of the author. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, - * OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, + * OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * - * Ownership of this code remains solely with the original author. Unauthorized + * Ownership of this code remains solely with the original author. Unauthorized * use of this Software is strictly prohibited. - + * @summary Validation utility for validating and constraining configuration values. * @description Validation utility for validating and constraining configuration values. * @module ValidationUtils * @requires Logger * @exports ValidationUtils - * @version 0.1.0 + * @version 0.2.0 * @since 0.1.0 */ 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 { constructor(IloggerEnabled, IloggerLevel) { @@ -64,7 +78,7 @@ class ValidationUtils { delete config[key]; } } - + // Validate each key in the schema and loop over wildcards if they are not in schema for ( const key in schema ) { @@ -74,7 +88,7 @@ class ValidationUtils { const fieldSchema = schema[key]; const { rules = {} } = fieldSchema; - + // Default to the schema's default value if the key is missing if (config[key] === undefined) { if (fieldSchema.default === undefined) { @@ -105,77 +119,58 @@ class ValidationUtils { configValue = config[key] !== undefined ? config[key] : fieldSchema.default; } - // Attempt to parse the value to the expected type if possible - switch (rules.type) { - - 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; - - case "machineCurve": - validatedConfig[key] = this.validateMachineCurve(configValue,fieldSchema.default); - continue; - - case "integer": - validatedConfig[key] = this.validateInteger(configValue, rules, fieldSchema, name, key); - continue; - - case undefined: - // If we see 'rules.schema' but no 'rules.type', treat it like an object: - if (rules.schema && !rules.type) { - // Log a warning and skip the extra pass for nested schema - this.logger.warn( - `${name}.${key} has a nested schema but no type. ` + - `Treating it as type="object" to skip extra pass.` - ); - } else { - // Otherwise, fallback to your existing "validateUndefined" logic - validatedConfig[key] = this.validateUndefined(configValue, fieldSchema, name, key); - } - continue; - - default: - this.logger.warn(`${name}.${key} has an unknown validation type: ${rules.type}. Skipping validation.`); - validatedConfig[key] = fieldSchema.default; - continue; + // Handle curve types (they use continue, so handle separately) + if (rules.type === "curve") { + validatedConfig[key] = validateCurve(configValue, fieldSchema.default, this.logger); + continue; } - + if (rules.type === "machineCurve") { + validatedConfig[key] = validateMachineCurve(configValue, fieldSchema.default, this.logger); + continue; + } + + // Handle object type (needs recursive validateSchema reference) + if (rules.type === "object") { + validatedConfig[key] = validateObject( + configValue, rules, fieldSchema, name, key, + (c, s, n) => this.validateSchema(c, s, n), + this.logger + ); + continue; + } + + // Handle undefined type + if (rules.type === undefined) { + if (rules.schema && !rules.type) { + this.logger.warn( + `${name}.${key} has a nested schema but no type. ` + + `Treating it as type="object" to skip extra pass.` + ); + } else { + validatedConfig[key] = this.validateUndefined(configValue, fieldSchema, name, key); + } + continue; + } + + // 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.`); + validatedConfig[key] = fieldSchema.default; + continue; + } + // Assign the validated or converted value validatedConfig[key] = configValue; } - + // Ignore unknown keys by not processing them at all this.logger.info(`Validation completed for ${name}.`); return validatedConfig; - } + } removeUnwantedKeys(obj) { @@ -203,332 +198,6 @@ class ValidationUtils { } 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) { if (typeof configValue === "object" && !Array.isArray(configValue)) { @@ -537,7 +206,7 @@ class ValidationUtils { // Recursively validate the nested object return this.validateSchema( configValue || {}, fieldSchema, `${name}.${key}`); - } + } else { this.logger.warn(`${name}.${key} has no defined rules. Using default value.`); return fieldSchema.default; diff --git a/src/helper/validators/collectionValidators.js b/src/helper/validators/collectionValidators.js new file mode 100644 index 0000000..14e4794 --- /dev/null +++ b/src/helper/validators/collectionValidators.js @@ -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 }; diff --git a/src/helper/validators/curveValidator.js b/src/helper/validators/curveValidator.js new file mode 100644 index 0000000..472c231 --- /dev/null +++ b/src/helper/validators/curveValidator.js @@ -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 +}; diff --git a/src/helper/validators/typeValidators.js b/src/helper/validators/typeValidators.js new file mode 100644 index 0000000..fbcbccf --- /dev/null +++ b/src/helper/validators/typeValidators.js @@ -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, +};