src/CelebrateUtils.js
/* eslint-disable new-cap */
const { Joi, CelebrateError, Segments } = require('celebrate');
const Utils = require('./Utils');
function _isJoiObject(obj) {
return Boolean(obj.type === 'object' && obj.$_terms);
}
function _getJoiObjectKeys(obj) {
return obj.$_terms.keys;
}
function _getTypeFromjoiSchema(joiSchema) {
const { type } = joiSchema;
if (['string', 'array', 'object', 'boolean'].includes(type)) {
return joiSchema.type;
}
if (type === 'number') {
return (
(joiSchema._rules.some((e) => e.name === 'integer') && 'integer') ||
'number'
);
}
return 'string';
}
function _getMinMaxFromSchemaDefition(joiSchema) {
let min = joiSchema._rules.find((r) => r.name === 'min');
min = (min && min.args.limit) || null;
let max = joiSchema._rules.find((r) => r.name === 'max');
max = (max && max.args.limit) || null;
return {
min,
max
};
}
function _setMinMaxInSwaggerSchema(type, joiSchema, swaggerSchema) {
const { min, max } = _getMinMaxFromSchemaDefition(joiSchema);
let minKey = 'minimum';
let maxKey = 'maximum';
if (type === 'string') {
minKey = 'minLength';
maxKey = 'maxLength';
} else if (type === 'array') {
minKey = 'minItems';
maxKey = 'maxItems';
}
swaggerSchema[minKey] = min;
swaggerSchema[maxKey] = max;
}
function _setSwaggerPropsForObject(type, joiSchema, swaggerSchema) {
if (
!(
type === 'object' &&
_getJoiObjectKeys(joiSchema) &&
_getJoiObjectKeys(joiSchema).length > 0
)
)
return;
const requiredProperties = [];
const objectjoiSchemaMap = {};
_getJoiObjectKeys(joiSchema).forEach((objectSchema) => {
if (objectSchema.schema._flags.presence === 'required') {
requiredProperties.push(objectSchema.key);
}
objectjoiSchemaMap[objectSchema.key] = _getSchemaDefinitionForSwagger(
objectSchema.schema
);
});
swaggerSchema.required =
(requiredProperties.length > 0 && requiredProperties) || null;
swaggerSchema.properties = objectjoiSchemaMap;
}
function _setSwaggerPropsForArray(type, joiSchema, swaggerSchema) {
if (type !== 'array') return;
const hasItemSchema = joiSchema.$_terms.items.length > 0;
swaggerSchema.items =
(hasItemSchema &&
_getSchemaDefinitionForSwagger(joiSchema.$_terms.items[0])) ||
{};
}
function _setMultipleOfSwaggerSchema(joiSchema, swaggerSchema) {
const multipleOf = joiSchema._rules.find((r) => r.name === 'multiple');
swaggerSchema.multipleOf = (multipleOf && multipleOf.args.base) || null;
}
function _setPatternSwaggerSchema(joiSchema, swaggerSchema) {
const pattern = joiSchema._rules.find((r) => r.name === 'pattern');
swaggerSchema.pattern = (pattern && String(pattern.args.regex)) || null;
}
function _setDefaultValueForSwaggerSchema(joiSchema, swaggerSchema) {
const defaultValue = joiSchema._flags.default;
swaggerSchema.default =
(defaultValue && JSON.stringify(defaultValue)) || null;
}
function _setSwaggerPropsEnums(joiSchema, swaggerSchema) {
if (!joiSchema._valids) return;
const validValues = [...joiSchema._valids._values.values()];
swaggerSchema.enum = validValues;
}
function _setSwaggerPropsNullable(swaggerSchema) {
if (swaggerSchema.enum && swaggerSchema.enum.includes(null)) {
swaggerSchema.nullable = true;
}
}
function _getSchemaDefinitionForSwagger(joiSchema) {
const type = _getTypeFromjoiSchema(joiSchema);
const swaggerSchema = {
type
};
_setDefaultValueForSwaggerSchema(joiSchema, swaggerSchema);
_setMultipleOfSwaggerSchema(joiSchema, swaggerSchema);
_setPatternSwaggerSchema(joiSchema, swaggerSchema);
_setSwaggerPropsEnums(joiSchema, swaggerSchema);
_setSwaggerPropsNullable(swaggerSchema);
_setMinMaxInSwaggerSchema(type, joiSchema, swaggerSchema);
_setSwaggerPropsForObject(type, joiSchema, swaggerSchema);
_setSwaggerPropsForArray(type, joiSchema, swaggerSchema);
Utils.clearNullValuesInObject(swaggerSchema);
return swaggerSchema;
}
function _getAllSwaggerParamsFromValidationSchema(schema, paramIn) {
return Object.keys(schema).map((key) => {
const joiSchema = schema[key];
const schemaDefinition = _getSchemaDefinitionForSwagger(joiSchema);
const isRequired = joiSchema._flags.presence === 'required';
const swaggerParams = {
name: key,
in: paramIn,
required: isRequired,
schema: schemaDefinition
};
if (paramIn === 'fileUpload') {
swaggerParams.type = 'file';
swaggerParams.in = 'formData';
}
return swaggerParams;
});
}
function _isJoiAny(schema) {
return Boolean(schema.type === 'any' && schema.$_terms);
}
function _getSwaggerParamsForBody(bodySchema) {
const joiSchema =
_isJoiObject(bodySchema) || _isJoiAny(bodySchema)
? bodySchema
: Joi.object(bodySchema);
const swaggerSchema = {
type: 'object'
};
_setSwaggerPropsForObject('object', joiSchema, swaggerSchema);
Utils.clearNullValuesInObject(swaggerSchema);
return {
name: 'body',
in: 'body',
schema: swaggerSchema
};
}
function _getObjectNormalizedSchema(schema) {
if (_isJoiObject(schema)) {
return _getJoiObjectKeys(schema).reduce((map, schemaObj) => {
map[schemaObj.key] = schemaObj.schema;
return map;
}, {});
}
return schema;
}
function _lowercaseHeaderSchemaKeys(headerSchema, paramLocation = 'header') {
if (paramLocation === 'header') {
Object.keys(headerSchema).forEach((key) => {
headerSchema[key.toLowerCase()] = headerSchema[key];
delete headerSchema[key];
});
}
}
function _addSwaggerParamsForNonBodyProps(parameterKeyMap, parameters) {
Object.keys(parameterKeyMap).forEach((paramLocation) => {
const normalizedSchema = _getObjectNormalizedSchema(
parameterKeyMap[paramLocation]
);
_lowercaseHeaderSchemaKeys(normalizedSchema, paramLocation);
const swaggerParams = _getAllSwaggerParamsFromValidationSchema(
normalizedSchema,
paramLocation
);
parameters.push(...swaggerParams);
});
}
function joiSchemaToSwaggerRequestParameters(validationSchema) {
if (!validationSchema) return [];
const {
query,
params: path,
headers: header,
body,
fileUpload
} = validationSchema;
const parameters = [];
if (body && Object.keys(body).length > 0) {
parameters.push(_getSwaggerParamsForBody(body));
}
const parameterKeyMap = { query, path, header, fileUpload };
Utils.clearNullValuesInObject(parameterKeyMap);
_addSwaggerParamsForNonBodyProps(parameterKeyMap, parameters);
return parameters;
}
function lowercaseHeaderSchemaProperties(validationSchema) {
if (!validationSchema.headers) return;
const { headers } = validationSchema;
if (_isJoiObject(headers)) {
_getJoiObjectKeys(validationSchema.headers).forEach((obj) => {
obj.key = obj.key.toLowerCase();
});
const byKey = validationSchema.headers._ids._byKey;
const keysForById = [...byKey.keys()];
keysForById.forEach((key) => {
const lowercaseKey = key.toLowerCase();
const mapValue = byKey.get(key);
mapValue.id = mapValue.id.toLowerCase();
byKey.set(lowercaseKey, mapValue);
byKey.delete(key);
});
} else {
_lowercaseHeaderSchemaKeys(validationSchema.headers);
}
}
function getSanitizedValidationSchema(validationSchema) {
if (
validationSchema.fileUpload &&
Object.keys(validationSchema).length === 1
) {
return null;
}
const newValidationSchema = { ...validationSchema };
delete newValidationSchema.fileUpload;
return newValidationSchema;
}
function getCelebrateValidationMiddlewareForFileUpload(fileUploadValidation) {
let schema = fileUploadValidation;
if (!_isJoiObject(schema)) {
schema = Joi.object(fileUploadValidation);
}
return async (req, _, next) => {
const { file, files } = req;
const testObj = {
...(file && { file }),
...(files && { files })
};
try {
await schema.validateAsync(testObj);
return next();
} catch (error) {
error.isJoi = true;
return next(
new CelebrateError(error, Segments.BODY, { celebrated: true })
);
}
};
}
module.exports = {
joiSchemaToSwaggerRequestParameters,
lowercaseHeaderSchemaProperties,
getCelebrateValidationMiddlewareForFileUpload,
getSanitizedValidationSchema
};