express-cargo
Version:
express middleware for class-based request parsing
128 lines (127 loc) • 4.81 kB
JavaScript
import { CargoFieldError, CargoValidationError, CargoTransformFieldError } from './types';
import { CargoClassMetadata } from './metadata';
function getErrorKey(sourceKey, currentKey) {
return sourceKey ? `${sourceKey}.${currentKey}` : currentKey;
}
function bindObject(objectClass, sources, errors, sourceKey = '') {
const metaClass = new CargoClassMetadata(objectClass.prototype);
const targetObject = new objectClass();
const fields = metaClass.getFieldList();
const virtualFields = [];
for (const property of fields){
const meta = metaClass.getFieldMetadata(property);
if (!meta) continue;
let value;
const metaKey = meta.getKey();
const key = typeof metaKey === 'string' ? metaKey : metaKey.description;
if (!key) {
errors.push(new CargoFieldError(getErrorKey(sourceKey, key), 'empty string or symbol is not allowed'));
continue;
}
const requestTransformer = meta.getRequestTransformer();
if (requestTransformer) {
try {
value = requestTransformer(sources.req);
} catch (error) {
errors.push(new CargoTransformFieldError(property, `Error while computing request transform field: ${error instanceof Error ? error.message : String(error)}`));
continue;
}
if (value === undefined || value === null) {
if (meta.getOptional()) {
targetObject[property] = null;
} else {
errors.push(new CargoFieldError(getErrorKey(sourceKey, key), `${key} is required`));
}
} else {
targetObject[property] = value;
}
continue;
}
if (meta.getVirtualTransformer()) {
virtualFields.push(property);
continue;
}
const currentSource = meta.getSource();
const currentSourceData = sources[currentSource];
if (currentSourceData) {
value = currentSourceData[key];
}
if (value === undefined || value === null) {
if (meta.getOptional()) {
targetObject[property] = null;
continue;
} else {
errors.push(new CargoFieldError(getErrorKey(sourceKey, key), `${key} is required`));
continue;
}
}
switch(meta.type){
case String:
targetObject[property] = String(value);
break;
case Number:
targetObject[property] = isNaN(Number(value)) ? value : Number(value);
break;
case Boolean:
targetObject[property] = value === true || value === 'true';
break;
case Date:
targetObject[property] = new Date(value);
break;
default:
{
const nextSources = {
...sources,
[currentSource]: value
};
targetObject[property] = bindObject(meta.type, nextSources, errors, getErrorKey(sourceKey, key));
break;
}
}
const transformer = meta.getTransformer();
if (transformer) {
targetObject[property] = transformer(targetObject[property]);
}
for (const rule of meta.getValidators()){
if (!rule.validate(value)) {
errors.push(new CargoFieldError(key, rule.message));
}
}
}
for (const property of virtualFields){
const meta = metaClass.getFieldMetadata(property);
const transformer = meta.getVirtualTransformer();
try {
targetObject[property] = transformer(targetObject);
} catch (error) {
errors.push(new CargoTransformFieldError(property, `Error while computing virtual field: ${error instanceof Error ? error.message : String(error)}`));
}
}
return targetObject;
}
export function bindingCargo(cargoClass) {
return (req, res, next)=>{
try {
const errors = [];
const sources = {
req: req,
body: req.body,
query: req.query,
uri: req.params,
header: req.headers,
session: req.session
};
const cargo = bindObject(cargoClass, sources, errors);
if (errors.length > 0) {
throw new CargoValidationError(errors);
}
req._cargo = cargo;
next();
} catch (err) {
next(err);
}
};
}
export function getCargo(req) {
return req._cargo;
}