@nestjs-mod/common
Version:
A collection of utilities for unifying NestJS applications and modules
184 lines • 7.73 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.configTransform = configTransform;
exports.getConfigModelMetadata = getConfigModelMetadata;
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'));
}
/**
* Transforms and validates configuration data based on model metadata
*/
async function configTransform({ model, data, rootOptions }) {
// Initialize root options
const options = {
logger: new common_1.ConsoleLogger('configTransform'),
...rootOptions,
};
const validator = loadValidator(options?.validatorPackage);
const { modelPropertyOptions, modelOptions } = getConfigModelMetadata(model);
// Build info object for documentation and debugging
const info = {
modelPropertyOptions,
modelOptions: modelOptions || {},
validations: {},
};
// Transform data with allowed fields
const transformedData = transformDataFields(data, modelPropertyOptions, options, modelOptions);
// Build validation info
buildValidationInfo(transformedData, modelPropertyOptions, info);
// Create model instances for validation
const { emptyInstance, dataInstance } = createModelInstances(model, transformedData);
// Validate the configuration
await validateConfiguration(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 };
}
// ============= Transformation Helpers =============
/**
* Transforms data fields based on model property options
*/
function transformDataFields(data, modelPropertyOptions, rootOptions, modelOptions) {
const transformedData = {};
for (const propertyOptions of modelPropertyOptions) {
const hasValue = data[propertyOptions.originalName] !== undefined;
if (propertyOptions.transform?.transform) {
transformedData[propertyOptions.originalName] = hasValue
? propertyOptions.transform.transform({
modelRootOptions: rootOptions,
modelOptions: modelOptions || {},
obj: data,
options: propertyOptions,
value: data[propertyOptions.originalName],
})
: propertyOptions.default;
}
else {
transformedData[propertyOptions.originalName] = hasValue
? data[propertyOptions.originalName]
: propertyOptions.default;
}
}
return transformedData;
}
/**
* Builds validation info from model property options
*/
function buildValidationInfo(data, modelPropertyOptions, info) {
for (const propertyOptions of modelPropertyOptions) {
info.validations[propertyOptions.originalName] = {
constraints: {},
value: data?.[propertyOptions.originalName],
};
}
}
/**
* 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: {} };
}
}
// ============= Validation Logic =============
/**
* Validates the configuration and populates validation constraints
*/
async function validateConfiguration(emptyInstance, dataInstance, validator, rootOptions, modelOptions, info) {
const shouldSkipValidation = rootOptions?.skipValidation || modelOptions?.skipValidation;
if (shouldSkipValidation) {
return;
}
const validatorOptions = rootOptions?.validatorOptions || modelOptions?.validatorOptions || constants_1.CONFIG_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.ConfigModelValidationErrors(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 configuration model metadata from the model class
*/
function getConfigModelMetadata(model) {
const modelPropertyOptions = Reflect.getMetadata(constants_1.CONFIG_MODEL_PROPERTIES_METADATA, model) || [];
const modelOptions = Reflect.getMetadata(constants_1.CONFIG_MODEL_METADATA, model);
return { modelPropertyOptions, modelOptions };
}
//# sourceMappingURL=utils.js.map