Files
generalFunctions/src/helper/validationUtils.js
znetsixe f96476bd23 Merge commit '12fce6c' into HEAD
# Conflicts:
#	index.js
#	src/configs/index.js
#	src/configs/machineGroupControl.json
#	src/helper/assetUtils.js
#	src/helper/childRegistrationUtils.js
#	src/helper/configUtils.js
#	src/helper/logger.js
#	src/helper/menuUtils.js
#	src/helper/menuUtils_DEPRECATED.js
#	src/helper/outputUtils.js
#	src/helper/validationUtils.js
#	src/measurements/Measurement.js
#	src/measurements/MeasurementContainer.js
#	src/measurements/examples.js
#	src/outliers/outlierDetection.js
2026-03-31 18:07:57 +02:00

231 lines
9.3 KiB
JavaScript

/**
* @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
* 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
* prior written permission from the author.
*
* 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
* SOFTWARE.
*
* 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.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) {
const loggerEnabled = IloggerEnabled ?? true;
const loggerLevel = IloggerLevel ?? "warn";
this.logger = new Logger(loggerEnabled, loggerLevel, 'ValidationUtils');
this._onceLogCache = new Set();
}
_logOnce(level, onceKey, message) {
if (onceKey && this._onceLogCache.has(onceKey)) {
return;
}
if (onceKey) {
this._onceLogCache.add(onceKey);
}
if (typeof this.logger?.[level] === "function") {
this.logger[level](message);
}
}
constrain(value, min, max) {
if (typeof value !== "number") {
this.logger?.warn(`Value '${value}' is not a number. Defaulting to ${min}.`);
return min;
}
return Math.min(Math.max(value, min), max);
}
validateSchema(config, schema, name) {
const validatedConfig = {};
let configValue;
// 1. Remove any unknown keys (keys not defined in the schema).
// Log a warning and omit them from the final config.
for (const key of Object.keys(config)) {
if (!(key in schema)) {
this.logger.warn(
`[${name}] Unknown key '${key}' found in config. Removing it.`
);
delete config[key];
}
}
// Validate each key in the schema and loop over wildcards if they are not in schema
for ( const key in schema ) {
if (key === "rules" || key === "description" || key === "schema") {
continue;
}
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) {
// If there's a nested schema, go deeper with an empty object rather than logging "no rule"
if (rules.schema) {
this.logger.warn(`${name}.${key} has no default, but has a nested schema.`);
validatedConfig[key] = this.validateSchema({}, rules.schema, `${name}.${key}`);
}
else {
this.logger.info(
`There is no rule for ${name}.${key} and no default value. ` +
`Using full schema value but validating deeper levels first...`
);
const SubObject = this.validateSchema({}, fieldSchema, `${name}.${key}`);
validatedConfig[key] = SubObject;
continue;
}
} else {
this.logger.debug(`No value provided for ${name}.${key}. Using default value.`);
configValue = fieldSchema.default;
}
//continue;
} else {
// Use the provided value if it exists, otherwise use the default value
configValue = config[key] !== undefined ? config[key] : fieldSchema.default;
}
// 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) {
if (Array.isArray(obj)) {
return obj.map((item) => this.removeUnwantedKeys(item));
}
if (obj && typeof obj === "object") {
const newObj = {};
for (const [k, v] of Object.entries(obj)) {
// Skip or remove keys like 'default', 'rules', 'description', etc.
if (["rules", "description"].includes(k)) {
continue;
}
if(v && typeof v === "object" && "default" in v){
//put the default value in the object
newObj[k] = v.default;
continue;
}
newObj[k] = this.removeUnwantedKeys(v);
}
return newObj;
}
return obj;
}
validateUndefined(configValue, fieldSchema, name, key) {
if (typeof configValue === "object" && !Array.isArray(configValue)) {
this.logger.debug(`${name}.${key} has no defined rules but is an object going 1 level deeper.`);
// 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;
}
}
}
module.exports = ValidationUtils;