UNPKG

@nestjs-mod/common

Version:

A collection of utilities for unifying NestJS applications and modules

258 lines 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.envTransform = envTransform; exports.getEnvModelMetadata = getEnvModelMetadata; const common_1 = require("@nestjs/common"); const load_package_1 = require("../utils/load-package"); const constants_1 = require("./constants"); const errors_1 = require("./errors"); // ============= Validation Helpers ============= /** * Loads the class-validator package, using provided package or loading it dynamically */ function loadValidator(validatorPackage) { return validatorPackage || (0, load_package_1.loadPackage)('class-validator', () => require('class-validator')); } // ============= Main Transformation Function ============= /** * Transforms and validates environment data based on model metadata */ async function envTransform({ model, data, rootOptions, }) { const options = { logger: new common_1.ConsoleLogger('envTransform'), ...rootOptions, }; const validator = loadValidator(options?.validatorPackage); const { modelPropertyOptions, modelOptions } = getEnvModelMetadata(model); const info = { modelPropertyOptions, modelOptions: modelOptions || {}, validations: {}, }; const propertyNameFormatters = options?.propertyNameFormatters || modelOptions?.propertyNameFormatters || []; // Process each property with formatters and extractors for (const propertyOptions of modelPropertyOptions) { processPropertyWithFormatters(propertyOptions, data, propertyNameFormatters, options, modelOptions, info); } // Transform property values transformPropertyValues(data, modelPropertyOptions, options, modelOptions, info); // Create model instances for validation const { emptyInstance, dataInstance } = createModelInstances(model, data); // Validate the environment configuration await validateEnvironment(emptyInstance, dataInstance, validator, options, modelOptions, info); // Log debug information if enabled logDebugInfo(options, modelOptions, info); // Copy validated data back to original data object copyValidatedData(data, dataInstance, modelPropertyOptions); return { data, info }; } // ============= Property Processing ============= /** * Processes a single property with all formatters and extractors */ function processPropertyWithFormatters(propertyOptions, data, propertyNameFormatters, rootOptions, modelOptions, info) { const demoMode = rootOptions?.demoMode || modelOptions?.demoMode || false; for (const propertyNameFormatter of propertyNameFormatters) { const formattedPropertyExample = propertyNameFormatter.example({ modelRootOptions: rootOptions, modelOptions: modelOptions || {}, propertyOptions, }); const formattedPropertyName = propertyNameFormatter.format({ modelRootOptions: rootOptions, modelOptions: modelOptions || {}, propertyOptions, }); // Extract property values from various sources const propertyValueExtractors = extractPropertyValues(data, propertyOptions, formattedPropertyName, demoMode, rootOptions, modelOptions); // Initialize validation info for this property initializeValidationInfo(propertyOptions, info); // Add formatter and extractor info to validation info.validations[propertyOptions.originalName].propertyNameFormatters.push({ name: propertyNameFormatter.name, value: formattedPropertyName, example: formattedPropertyExample, }); info.validations[propertyOptions.originalName].propertyValueExtractors = [ ...info.validations[propertyOptions.originalName].propertyValueExtractors, ...propertyValueExtractors, ]; } } /** * Extracts property values from all configured extractors */ function extractPropertyValues(data, propertyOptions, formattedPropertyName, demoMode, rootOptions, modelOptions) { const propertyValueExtractors = (rootOptions?.propertyValueExtractors ?? modelOptions?.propertyValueExtractors ?? []).map((extractor) => { // Set demo mode if extractor supports it if (extractor.setDemoMode) { extractor.setDemoMode(demoMode); } const result = { name: extractor.name, example: extractor.example({ obj: data, modelRootOptions: rootOptions, modelOptions: modelOptions || {}, propertyOptions, formattedPropertyName, }), demoMode, value: extractor.extract({ obj: data, modelRootOptions: rootOptions, modelOptions: modelOptions || {}, propertyOptions, formattedPropertyName, }), }; // Reset demo mode if (extractor.setDemoMode) { extractor.setDemoMode(false); } return result; }); return propertyValueExtractors; } /** * Initializes validation info for a property if not already present */ function initializeValidationInfo(propertyOptions, info) { if (info.validations[propertyOptions.originalName] === undefined) { info.validations[propertyOptions.originalName] = { constraints: {}, value: undefined, propertyNameFormatters: [], propertyValueExtractors: [], }; } } // ============= Value Transformation ============= /** * Transforms property values using transformers or extracted values */ function transformPropertyValues(data, modelPropertyOptions, rootOptions, modelOptions, info) { for (const propertyOptions of modelPropertyOptions) { const extractedValue = info.validations[propertyOptions.originalName].propertyValueExtractors.find((e) => e.value !== undefined)?.value; const currentValue = data[propertyOptions.originalName]; const value = extractedValue !== undefined ? extractedValue : currentValue; if (propertyOptions.transform?.transform) { data[propertyOptions.originalName] = value !== undefined ? propertyOptions.transform.transform({ modelRootOptions: rootOptions, modelOptions: modelOptions || {}, obj: data, options: propertyOptions, value, }) : propertyOptions.default; } else { data[propertyOptions.originalName] = value !== undefined ? value : propertyOptions.default; } info.validations[propertyOptions.originalName].value = data[propertyOptions.originalName]; } } // ============= Validation Logic ============= /** * Creates empty and data-filled model instances */ function createModelInstances(model, data) { try { const emptyInstance = new model(); const dataInstance = Object.assign(new model(), data); return { emptyInstance, dataInstance }; } catch { return { emptyInstance: {}, dataInstance: {} }; } } /** * Validates the environment configuration */ async function validateEnvironment(emptyInstance, dataInstance, validator, rootOptions, modelOptions, info) { const shouldSkipValidation = rootOptions?.skipValidation || modelOptions?.skipValidation; if (shouldSkipValidation) { return; } const validatorOptions = rootOptions?.validatorOptions || modelOptions?.validatorOptions || constants_1.ENV_MODEL_CLASS_VALIDATOR_OPTIONS; // Validate data instance for errors const validationErrors = (await validator.validate(dataInstance, validatorOptions)).filter((error) => error.property); // Validate empty instance to collect constraints for documentation const emptyValidationErrors = (await validator.validate(emptyInstance, validatorOptions)).filter((error) => error.property); // Collect constraints from empty instance validation for (const error of emptyValidationErrors) { if (info.validations[error.property]) { info.validations[error.property].constraints = error?.constraints || {}; } } // Throw error if validation failed if (validationErrors.length > 0) { throwError(validationErrors, info, rootOptions, modelOptions); } } /** * Throws validation error with proper logging */ function throwError(errors, info, rootOptions, modelOptions) { const error = new errors_1.EnvModelValidationErrors(errors, info); const logger = rootOptions?.logger || modelOptions?.logger; if (logger?.error) { const debug = rootOptions?.debug || modelOptions?.debug || process.env['DEBUG']; const visibleValidations = buildVisibleValidations(info); logger.error(!debug ? error.message : { ...info, validations: visibleValidations, modelPropertyOptions: info.modelPropertyOptions.filter((o) => !o.hideValueFromOutputs && visibleValidations[o.originalName]), }); } throw error; } /** * Builds visible validations (excluding hidden values) */ function buildVisibleValidations(info) { return Object.fromEntries(Object.entries(info.validations).filter(([key, value]) => { return (Object.keys(value.constraints || {}).length > 0 && !info.modelPropertyOptions.some((o) => o.hideValueFromOutputs && o.originalName === key)); })); } /** * Logs debug information if debug mode is enabled */ function logDebugInfo(rootOptions, modelOptions, info) { const debug = rootOptions?.debug || modelOptions?.debug || process.env['DEBUG']; const logger = rootOptions?.logger || modelOptions?.logger; if (debug && logger?.debug) { logger.debug(JSON.stringify({ ...info, validations: Object.fromEntries(Object.entries(info.validations).filter(([key]) => { return !info.modelPropertyOptions.some((o) => o.hideValueFromOutputs && o.originalName === key); })), modelPropertyOptions: info.modelPropertyOptions.filter((o) => !o.hideValueFromOutputs), })); } } /** * Copies validated data from instance back to original data object */ function copyValidatedData(data, dataInstance, modelPropertyOptions) { for (const propertyMetadata of modelPropertyOptions) { data[propertyMetadata.originalName] = dataInstance[propertyMetadata.originalName]; } } // ============= Metadata Extraction ============= /** * Extracts environment model metadata from the model class */ function getEnvModelMetadata(model) { const modelPropertyOptions = Reflect.getMetadata(constants_1.ENV_MODEL_PROPERTIES_METADATA, model) || []; const modelOptions = Reflect.getMetadata(constants_1.ENV_MODEL_METADATA, model); return { modelPropertyOptions, modelOptions }; } //# sourceMappingURL=utils.js.map