UNPKG

felicity

Version:

Javascript object instantiation from Joi schema

152 lines (119 loc) 5.31 kB
'use strict'; const Hoek = require('@hapi/hoek'); const Joi = require('joi'); const Helpers = require('./helpers'); const internals = {}; internals.joiDictionary = { string: null, boolean: false, date: null, binary: null, func: null, function: null, number: 0, array: [], object: {}, alternatives: null, any: null }; internals.reachDelete = function (target, splitPath) { if (!Array.isArray(splitPath)) { splitPath = [splitPath]; } if (splitPath.length === 1) { delete target[splitPath[0]]; } else { const newTarget = target[splitPath[0]]; const newPath = splitPath.slice(1).join('.'); const newPathExists = Hoek.reach(newTarget, newPath) !== undefined; if (newPathExists) { internals.reachDelete(newTarget, newPath); } } }; const generate = function (instance, schemaInput, options) { let input = Hoek.reach(options, 'input'); const config = Hoek.reach(options, 'config'); const ignoreDefaults = Hoek.reach(config, 'ignoreDefaults'); const schemaMapper = function (target, schema) { const schemaDescription = schema.describe(); if (schemaDescription.keys) { const parentPresence = Hoek.reach(schemaDescription, 'preferences.presence'); const childrenKeys = Object.keys(schemaDescription.keys); childrenKeys.forEach((childKey) => { const childSchemaDescription = schemaDescription.keys[childKey]; const flagsPresence = Hoek.reach(childSchemaDescription, 'flags.presence'); const flagsStrip = Hoek.reach(childSchemaDescription, 'flags.result') === 'strip'; const childIsRequired = flagsPresence === 'required'; const childIsOptional = (flagsPresence === 'optional') || (parentPresence === 'optional' && !childIsRequired); const childIsForbidden = flagsPresence === 'forbidden' || flagsStrip; if (childIsForbidden || (childIsOptional && !(Hoek.reach(config, 'includeOptional')))) { return; } const childType = childSchemaDescription.type; const childSchemaRaw = schema.$_terms.keys.filter((child) => { return child.key === childKey; })[0].schema; if (childType === 'function') { target[childKey] = Hoek.clone(internals.joiDictionary.function); } else if (childType === 'object') { target[childKey] = {}; schemaMapper(target[childKey], childSchemaRaw); } else if (childType === 'alternatives') { const hasTrueCase = childSchemaDescription.matches.filter((option) => { return option.ref && option.then; }).length > 0; const chosenAlternative = hasTrueCase ? childSchemaDescription.matches[0].then : childSchemaDescription.matches[0]; if (chosenAlternative.type === 'function') { target[childKey] = Hoek.clone(internals.joiDictionary.function); } else if (chosenAlternative.type === 'object') { target[childKey] = {}; schemaMapper(target[childKey], Joi.build(chosenAlternative)); } else if (chosenAlternative.schema) { target[childKey] = Hoek.clone(internals.joiDictionary[chosenAlternative.schema.type]); } else { const alternativeHasDefault = Hoek.reach(chosenAlternative, 'flags.default'); target[childKey] = (alternativeHasDefault && !ignoreDefaults) ? Helpers.getDefault(chosenAlternative) : Hoek.clone(internals.joiDictionary[chosenAlternative.type]); } } else { const childHasDefault = Hoek.reach(childSchemaDescription, 'flags.default'); target[childKey] = (childHasDefault && !ignoreDefaults) ? Helpers.getDefault(childSchemaDescription) : Hoek.clone(internals.joiDictionary[childType]); } }); } }; schemaMapper(instance, schemaInput); if (input) { const validateOptions = { abortEarly: false }; const validation = schemaInput.validate(input, validateOptions); input = validation.value; if (Hoek.reach(config, 'validateInput') && validation.error) { throw validation.error; } if (Hoek.reach(config, 'strictInput')) { const invalidInputValues = Hoek.reach(validation, 'error.details'); if (invalidInputValues) { invalidInputValues.forEach((error) => { internals.reachDelete(input, error.path); }); } } Hoek.merge(instance, input); } }; module.exports = generate;