UNPKG

nestjs-config-validator

Version:

Advanced configuration validator for NestJS with type-safe schema validation

266 lines (265 loc) 11.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AdvancedConfigValidator = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Продвинутый валидатор конфигурации с полной валидацией */ class AdvancedConfigValidator { constructor(configPath) { this.configData = {}; const configPathToUse = configPath || path.resolve(process.cwd(), 'config.json'); this.loadConfig(configPathToUse); } loadConfig(configPath) { if (!fs.existsSync(configPath)) { console.warn(`Config file not found at ${configPath}`); this.configData = {}; return; } try { const configContent = fs.readFileSync(configPath, 'utf-8'); this.configData = JSON.parse(configContent); } catch (error) { console.warn(`Failed to parse config file: ${error instanceof Error ? error.message : 'Unknown error'}`); this.configData = {}; } } /** * Получает значение из переменной окружения */ getEnvValue(envName) { return process.env[envName]; } /** * Преобразует строковое значение из переменной окружения в нужный тип */ parseEnvValue(value, type) { switch (type) { case 'string': return value; case 'number': const num = Number(value); return isNaN(num) ? undefined : num; case 'boolean': return value.toLowerCase() === 'true'; case 'enum': return value; default: return value; } } /** * Валидирует схему конфигурации */ validateSchema(schema, configSection) { const errors = []; const config = configSection ? this.configData[configSection] : this.configData; if (!config) { if (schema.required) { errors.push(`Configuration section '${schema.name}' is missing`); } return { valid: errors.length === 0, errors }; } // Валидируем только объекты с properties if (schema.type === 'object' && 'properties' in schema && schema.properties) { for (const property of schema.properties) { let value = config[property.name]; const propertyPath = `${schema.name}.${property.name}`; // Проверяем переменную окружения, если указана if (property.nodeEnv && value === undefined) { const envValue = this.getEnvValue(property.nodeEnv); if (envValue !== undefined) { value = this.parseEnvValue(envValue, property.type); config[property.name] = value; } } // Проверка обязательности if (property.required && value === undefined) { errors.push(`Required property '${propertyPath}' is missing`); continue; } // Если значение не задано, используем значение по умолчанию if (value === undefined && 'defaultValue' in property && property.defaultValue !== undefined) { config[property.name] = property.defaultValue; continue; } // Если значение все еще не задано и не обязательное, пропускаем if (value === undefined) { continue; } // Валидация по типу const typeError = this.validatePropertyType(property, value, propertyPath); if (typeError) { errors.push(typeError); } // Дополнительная валидация для enum if (property.type === 'enum') { const enumError = this.validateEnumProperty(property, value, propertyPath); if (enumError) { errors.push(enumError); } } } } return { valid: errors.length === 0, errors }; } /** * Валидирует тип свойства */ validatePropertyType(property, value, propertyPath) { switch (property.type) { case 'string': if (typeof value !== 'string') { return `Property '${propertyPath}' must be a string, got ${typeof value}`; } if (property.minLength && value.length < property.minLength) { return `Property '${propertyPath}' must be at least ${property.minLength} characters long`; } if (property.maxLength && value.length > property.maxLength) { return `Property '${propertyPath}' must be at most ${property.maxLength} characters long`; } if (property.pattern && !new RegExp(property.pattern).test(value)) { return `Property '${propertyPath}' does not match pattern ${property.pattern}`; } break; case 'number': if (typeof value !== 'number') { return `Property '${propertyPath}' must be a number, got ${typeof value}`; } if (property.min !== undefined && value < property.min) { return `Property '${propertyPath}' must be >= ${property.min}, got ${value}`; } if (property.max !== undefined && value > property.max) { return `Property '${propertyPath}' must be <= ${property.max}, got ${value}`; } break; case 'boolean': if (typeof value !== 'boolean') { return `Property '${propertyPath}' must be a boolean, got ${typeof value}`; } break; case 'object': if (typeof value !== 'object' || value === null || Array.isArray(value)) { return `Property '${propertyPath}' must be an object, got ${typeof value}`; } if (property.properties) { for (const prop of property.properties) { const propValue = value[prop.name]; if (prop.required && propValue === undefined) { return `Required property '${prop.name}' is missing in ${propertyPath}`; } if (propValue !== undefined) { const propError = this.validatePropertyType(prop, propValue, `${propertyPath}.${prop.name}`); if (propError) { return propError; } } } } break; case 'enum': if (!property.enum.includes(value)) { return `Property '${propertyPath}' must be one of [${property.enum.join(', ')}], got ${value}`; } break; } return null; } /** * Дополнительная валидация для enum свойств */ validateEnumProperty(property, value, propertyPath) { // Проверяем defaultValue if ('defaultValue' in property && property.defaultValue !== undefined && !property.enum.includes(property.defaultValue)) { return `Default value '${property.defaultValue}' for '${propertyPath}' is not in enum [${property.enum.join(', ')}]`; } // Проверяем examples if (property.examples) { for (const example of property.examples) { if (!property.enum.includes(example)) { return `Example value '${example}' for '${propertyPath}' is not in enum [${property.enum.join(', ')}]`; } } } return null; } /** * Получает валидированную конфигурацию */ getValidatedConfig(schema, configSection) { const result = this.validateSchema(schema, configSection); if (!result.valid) { throw new Error(`Configuration validation failed for ${schema.name}: ${result.errors.join(', ')}`); } const config = configSection ? this.configData[configSection] : this.configData; const validatedConfig = {}; // Применяем значения по умолчанию и возвращаем валидированную конфигурацию if (schema.type === 'object' && 'properties' in schema && schema.properties) { for (const property of schema.properties) { let value = config?.[property.name]; // Проверяем переменную окружения, если указана if (property.nodeEnv && value === undefined) { const envValue = this.getEnvValue(property.nodeEnv); if (envValue !== undefined) { value = this.parseEnvValue(envValue, property.type); } } validatedConfig[property.name] = value !== undefined ? value : ('defaultValue' in property ? property.defaultValue : undefined); } } return validatedConfig; } /** * Валидирует всю корневую схему */ validateRootSchema(rootSchema) { const errors = []; for (const schema of rootSchema.properties) { const result = this.validateSchema(schema, schema.name); if (!result.valid) { errors.push(...result.errors); } } return { valid: errors.length === 0, errors, }; } } exports.AdvancedConfigValidator = AdvancedConfigValidator;