@eluvio/elv-js-helpers
Version:
A collection of Javascript helper functions used by several Eluvio libraries.
79 lines (71 loc) • 3.06 kB
JavaScript
const defObjectModel = require('./defObjectModel')
const isObject = require('../Boolean/isObject')
const throwIfUndefined = require('../Validation/throwIfUndefined')
// TODO: add defaults clause
/**
* Returns an [ObjectModel](http://objectmodel.js.org/) which will validate that an input is:
*
* * A [Javascript Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
* * Satisfies the specified field definitions
* * Contains no extra fields
*
* The object returned by the function will also throw an exception if you attempt to add a field to it that is not
* in the original definition.
*
* NOTE: Copying the object using a function like Ramda's `assoc` function will not preserve this protection for the copy.
*
* @function
* @category ModelFactory
* @sig String -> Object -> (* -> Object | THROW)
* @param {String} name - the name of the generated Model
* @param {Object} def - The definition (field structure) of the Model to generate
* @returns {Function} Returns an [ObjectModel](http://objectmodel.js.org/) that can be called with an input
* @example
*
* 'use strict'
* const defSealedObjModel = require('@eluvio/elv-js-helpers/ModelFactory/defSealedObjModel')
*
* const PersonNameModel = defSealedObjModel('PersonName', {first: String, last: String})
*
* PersonNameModel(-1) //=> EXCEPTION: 'expecting Object, got Number -1'
*
* PersonNameModel({first: 'Arthur', last: 'Dent'}) //=> {"first":"Arthur","last":"Dent"}
*
* PersonNameModel({first: 'Arthur'}) //=> EXCEPTION: 'expecting last to be String, got undefined'
*
* PersonNameModel({first: 'A', last: 'D', species: 'human'}) //=> EXCEPTION: 'Unrecognized property name(s): species'
*
*/
const defSealedObjModel = (name, def) => {
throwIfUndefined('no model definition supplied', def)
const model = defObjectModel(name, def)
model.sealed = true
model.extend = () => {
throw new Error('Sealed models cannot be extended')
}
// const isPlainObject = obj => typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype
const checkUndeclaredProps = (obj, def, undeclaredProps, path) => {
if(!isObject(obj)) return
Object.keys(obj).forEach(key => {
let val = obj[key],
subpath = path ? path + '.' + key : key
if(isObject(obj) && isObject(def) && !Object.prototype.hasOwnProperty.call(def, key)) {
undeclaredProps.push(subpath)
} else if (isObject(val) && isObject(def)) {
checkUndeclaredProps(val, def[key], undeclaredProps, subpath)
}
})
}
return model.assert(
function hasNoUndeclaredProps(obj) {
if (!model.sealed) return true
let undeclaredProps = []
checkUndeclaredProps(obj, this.definition, undeclaredProps)
return undeclaredProps.length === 0 ? true : undeclaredProps
},
undeclaredProps =>
`Unrecognized property name(s): ${undeclaredProps}`
)
}
module.exports = defSealedObjModel